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

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

i18n fix

  • Property svn:eol-style set to native
File size: 33.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.search;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.io.PushbackReader;
8import java.io.StringReader;
9import java.text.Normalizer;
10import java.util.Collection;
11import java.util.Date;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14import java.util.regex.PatternSyntaxException;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.actions.search.PushbackTokenizer.Range;
18import org.openstreetmap.josm.actions.search.PushbackTokenizer.Token;
19import org.openstreetmap.josm.data.Bounds;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmUtils;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.RelationMember;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.tools.DateUtils;
27import org.openstreetmap.josm.tools.Geometry;
28
29/**
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
42 term?
43 term=term
44 term:term
45 term
46 </pre>
47
48 @author Imi
49 */
50public class SearchCompiler {
51
52 private boolean caseSensitive = false;
53 private boolean regexSearch = false;
54 private static String rxErrorMsg = marktr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}");
55 private static String rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}");
56 private PushbackTokenizer tokenizer;
57
58 public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) {
59 this.caseSensitive = caseSensitive;
60 this.regexSearch = regexSearch;
61 this.tokenizer = tokenizer;
62 }
63
64 abstract public static class Match {
65 abstract public boolean match(OsmPrimitive osm);
66
67 /**
68 * Tests whether one of the primitives matches.
69 */
70 protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) {
71 for (OsmPrimitive p : primitives) {
72 if (match(p))
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) {
83 if (!match(p))
84 return false;
85 }
86 return true;
87 }
88 }
89
90 public static class Always extends Match {
91 public static Always INSTANCE = new Always();
92 @Override public boolean match(OsmPrimitive osm) {
93 return true;
94 }
95 }
96
97 public static class Never extends Match {
98 @Override
99 public boolean match(OsmPrimitive osm) {
100 return false;
101 }
102 }
103
104 public static class Not extends Match {
105 private final Match match;
106 public Not(Match match) {this.match = match;}
107 @Override public boolean match(OsmPrimitive osm) {
108 return !match.match(osm);
109 }
110 @Override public String toString() {return "!"+match;}
111 public Match getMatch() {
112 return match;
113 }
114 }
115
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
134 public static class And extends Match {
135 private final Match lhs;
136 private final Match rhs;
137 public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
138 @Override public boolean match(OsmPrimitive osm) {
139 return lhs.match(osm) && rhs.match(osm);
140 }
141 @Override public String toString() {return lhs+" && "+rhs;}
142 public Match getLhs() {
143 return lhs;
144 }
145 public Match getRhs() {
146 return rhs;
147 }
148 }
149
150 public static class Or extends Match {
151 private final Match lhs;
152 private final Match rhs;
153 public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;}
154 @Override public boolean match(OsmPrimitive osm) {
155 return lhs.match(osm) || rhs.match(osm);
156 }
157 @Override public String toString() {return lhs+" || "+rhs;}
158 public Match getLhs() {
159 return lhs;
160 }
161 public Match getRhs() {
162 return rhs;
163 }
164 }
165
166 private static class Id extends Match {
167 private long id;
168 public Id(long id) {
169 this.id = id;
170 }
171 @Override public boolean match(OsmPrimitive osm) {
172 return id == 0?osm.isNew():osm.getUniqueId() == id;
173 }
174 @Override public String toString() {return "id="+id;}
175 }
176
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
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
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;
201
202 public KeyValue(String key, String value, boolean regexSearch, boolean caseSensitive) throws ParseError {
203 this.caseSensitive = caseSensitive;
204 if (regexSearch) {
205 int searchFlags = regexFlags(caseSensitive);
206
207 try {
208 this.keyPattern = Pattern.compile(key, searchFlags);
209 } catch (PatternSyntaxException e) {
210 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
211 } catch (Exception e) {
212 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()));
213 }
214 try {
215 this.valuePattern = Pattern.compile(value, searchFlags);
216 } catch (PatternSyntaxException e) {
217 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
218 } catch (Exception e) {
219 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()));
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) {
240 if (!osm.hasKeys())
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
250 for (String k: osm.keySet()) {
251 String v = osm.get(k);
252
253 Matcher matcherKey = keyPattern.matcher(k);
254 boolean matchedKey = matcherKey.find();
255
256 if (matchedKey) {
257 Matcher matcherValue = valuePattern.matcher(v);
258 boolean matchedValue = matcherValue.find();
259
260 if (matchedValue)
261 return true;
262 }
263 }
264 } else {
265 String mv = null;
266
267 if (key.equals("timestamp")) {
268 mv = DateUtils.fromDate(osm.getTimestamp());
269 } else {
270 mv = osm.get(key);
271 }
272
273 if (mv == null)
274 return false;
275
276 String v1 = caseSensitive ? mv : mv.toLowerCase();
277 String v2 = caseSensitive ? value : value.toLowerCase();
278
279 v1 = Normalizer.normalize(v1, Normalizer.Form.NFC);
280 v2 = Normalizer.normalize(v2, Normalizer.Form.NFC);
281 return v1.indexOf(v2) != -1;
282 }
283
284 return false;
285 }
286 @Override public String toString() {return key+"="+value;}
287 }
288
289 public static class ExactKeyValue extends Match {
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 {
303 if ("".equals(key))
304 throw new ParseError(tr("Key cannot be empty when tag operator is used. Sample use: key=value"));
305 this.key = key;
306 this.value = value == null?"":value;
307 if ("".equals(this.value) && "*".equals(key)) {
308 mode = Mode.NONE;
309 } else if ("".equals(this.value)) {
310 if (regexp) {
311 mode = Mode.MISSING_KEY_REGEXP;
312 } else {
313 mode = Mode.MISSING_KEY;
314 }
315 } else if ("*".equals(key) && "*".equals(this.value)) {
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 }
323 } else if ("*".equals(this.value)) {
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("*")) {
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) {
343 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()));
344 }
345 } else {
346 keyPattern = null;
347 }
348 if (regexp && this.value.length() > 0 && !this.value.equals("*")) {
349 try {
350 valuePattern = Pattern.compile(this.value, regexFlags(false));
351 } catch (PatternSyntaxException e) {
352 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
353 } catch (Exception e) {
354 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()));
355 }
356 } else {
357 valuePattern = null;
358 }
359 }
360
361 @Override
362 public boolean match(OsmPrimitive osm) {
363
364 if (!osm.hasKeys())
365 return mode == Mode.NONE;
366
367 switch (mode) {
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:
392 for (String key: osm.keySet()) {
393 if (keyPattern.matcher(key).matches()) {
394 if (mode == Mode.ANY_VALUE_REGEXP
395 || valuePattern.matcher(osm.get(key)).matches())
396 return true;
397 }
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;
406 }
407 throw new AssertionError("Missed state");
408 }
409
410 @Override
411 public String toString() {
412 return key + '=' + value;
413 }
414
415 }
416
417 private static class Any extends Match {
418 private final String search;
419 private final Pattern searchRegex;
420 private final boolean caseSensitive;
421
422 public Any(String s, boolean regexSearch, boolean caseSensitive) throws ParseError {
423 s = Normalizer.normalize(s, Normalizer.Form.NFC);
424 this.caseSensitive = caseSensitive;
425 if (regexSearch) {
426 try {
427 this.searchRegex = Pattern.compile(s, regexFlags(caseSensitive));
428 } catch (PatternSyntaxException e) {
429 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()));
430 } catch (Exception e) {
431 throw new ParseError(tr(rxErrorMsgNoPos, s, e.getMessage()));
432 }
433 this.search = s;
434 } else if (caseSensitive) {
435 this.search = s;
436 this.searchRegex = null;
437 } else {
438 this.search = s.toLowerCase();
439 this.searchRegex = null;
440 }
441 }
442
443 @Override public boolean match(OsmPrimitive osm) {
444 if (!osm.hasKeys() && osm.getUser() == null)
445 return search.equals("");
446
447 for (String key: osm.keySet()) {
448 String value = osm.get(key);
449 if (searchRegex != null) {
450
451 value = Normalizer.normalize(value, Normalizer.Form.NFC);
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 {
462 if (!caseSensitive) {
463 key = key.toLowerCase();
464 value = value.toLowerCase();
465 }
466
467 value = Normalizer.normalize(value, Normalizer.Form.NFC);
468
469 if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
470 return true;
471 }
472 }
473 return false;
474 }
475 @Override public String toString() {
476 return search;
477 }
478 }
479
480 private static class ExactType extends Match {
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;
489 } else
490 throw new ParseError(tr("Unknown primitive type: {0}. Allowed values are node, way or relation",
491 type));
492 }
493 @Override public boolean match(OsmPrimitive osm) {
494 return osm.getClass() == type;
495 }
496 @Override public String toString() {return "type="+type;}
497 }
498
499 private static class UserMatch extends Match {
500 private String user;
501 public UserMatch(String user) {
502 if (user.equals("anonymous")) {
503 this.user = null;
504 } else {
505 this.user = user;
506 }
507 }
508
509 @Override public boolean match(OsmPrimitive osm) {
510 if (osm.getUser() == null)
511 return user == null;
512 else
513 return osm.getUser().hasName(user);
514 }
515
516 @Override public String toString() {
517 return "user=" + user == null ? "" : user;
518 }
519 }
520
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
551 private abstract static class CountRange extends Match {
552
553 private long minCount;
554 private long maxCount;
555
556 public CountRange(long minCount, long maxCount) {
557 this.minCount = Math.min(minCount, maxCount);
558 this.maxCount = Math.max(minCount, maxCount);
559 }
560
561 protected abstract Long getCount(OsmPrimitive osm);
562
563 protected abstract String getCountString();
564
565 @Override
566 public boolean match(OsmPrimitive osm) {
567 Long count = getCount(osm);
568 if (count == null)
569 return false;
570 else
571 return (count >= minCount) && (count <= maxCount);
572 }
573
574 @Override
575 public String toString() {
576 return getCountString() + "=" + minCount + "-" + maxCount;
577 }
578 }
579
580
581
582 private static class NodeCountRange extends CountRange {
583
584 public NodeCountRange(long minCount, long maxCount) {
585 super(minCount, maxCount);
586 }
587
588 @Override
589 protected Long getCount(OsmPrimitive osm) {
590 if (!(osm instanceof Way))
591 return null;
592 else
593 return (long) ((Way) osm).getNodesCount();
594 }
595
596 @Override
597 protected String getCountString() {
598 return "nodes";
599 }
600 }
601
602 private static class TagCountRange extends CountRange {
603
604 public TagCountRange(long minCount, long maxCount) {
605 super(minCount, maxCount);
606 }
607
608 @Override
609 protected Long getCount(OsmPrimitive osm) {
610 return (long) osm.getKeys().size();
611 }
612
613 @Override
614 protected String getCountString() {
615 return "tags";
616 }
617 }
618
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
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
646 private static class Modified extends Match {
647 @Override public boolean match(OsmPrimitive osm) {
648 return osm.isModified() || osm.isNewOrUndeleted();
649 }
650 @Override public String toString() {return "modified";}
651 }
652
653 private static class Selected extends Match {
654 @Override public boolean match(OsmPrimitive osm) {
655 return Main.main.getCurrentDataSet().isSelected(osm);
656 }
657 @Override public String toString() {return "selected";}
658 }
659
660 private static class Incomplete extends Match {
661 @Override public boolean match(OsmPrimitive osm) {
662 return osm.isIncomplete();
663 }
664 @Override public String toString() {return "incomplete";}
665 }
666
667 private static class Untagged extends Match {
668 @Override public boolean match(OsmPrimitive osm) {
669 return !osm.isTagged() && !osm.isIncomplete();
670 }
671 @Override public String toString() {return "untagged";}
672 }
673
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
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 }
692 @Override public boolean match(OsmPrimitive osm) {
693 boolean isParent = false;
694
695 if (osm instanceof Way) {
696 for (Node n : ((Way)osm).getNodes()) {
697 isParent |= child.match(n);
698 }
699 } else if (osm instanceof Relation) {
700 for (RelationMember member : ((Relation)osm).getMembers()) {
701 isParent |= child.match(member.getMember());
702 }
703 }
704 return isParent;
705 }
706 @Override public String toString() {return "parent(" + child + ")";}
707 public Match getChild() {
708 return child;
709 }
710 }
711
712 public static class Child extends Match {
713 private final Match parent;
714
715 public Child(Match m) {
716 // "child" (null) should mean the same as "child()"
717 // (Always). I.e. match everything
718 if (m == null) {
719 parent = new Always();
720 } else {
721 parent = m;
722 }
723 }
724
725 @Override public boolean match(OsmPrimitive osm) {
726 boolean isChild = false;
727 for (OsmPrimitive p : osm.getReferrers()) {
728 isChild |= parent.match(p);
729 }
730 return isChild;
731 }
732 @Override public String toString() {return "child(" + parent + ")";}
733
734 public Match getParent() {
735 return parent;
736 }
737 }
738
739 /**
740 * Matches on the area of a closed way.
741 *
742 * @author Ole Jørgen Brønner
743 */
744 private static class Area extends CountRange {
745
746 public Area(long minCount, long maxCount) {
747 super(minCount, maxCount);
748 }
749
750 @Override
751 protected Long getCount(OsmPrimitive osm) {
752 if (!(osm instanceof Way && ((Way) osm).isClosed()))
753 return null;
754 Way way = (Way) osm;
755 return (long) Geometry.closedWayArea(way);
756 }
757
758 @Override
759 protected String getCountString() {
760 return "area";
761 }
762 }
763
764 /**
765 * Matches data within bounds.
766 */
767 private abstract static class InArea extends Match {
768
769 protected abstract Bounds getBounds();
770 protected final boolean all;
771 protected final Bounds bounds;
772
773 /**
774 * @param all if true, all way nodes or relation members have to be within source area;if false, one suffices.
775 */
776 public InArea(boolean all) {
777 this.all = all;
778 this.bounds = getBounds();
779 }
780
781 @Override
782 public boolean match(OsmPrimitive osm) {
783 if (!osm.isUsable())
784 return false;
785 else if (osm instanceof Node)
786 return bounds.contains(((Node) osm).getCoor());
787 else if (osm instanceof Way) {
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);
793 } else
794 return false;
795 }
796 }
797
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
828 public static class ParseError extends Exception {
829 public ParseError(String msg) {
830 super(msg);
831 }
832 public ParseError(Token expected, Token found) {
833 this(tr("Unexpected token. Expected {0}, found {1}", expected, found));
834 }
835 }
836
837 public static Match compile(String searchStr, boolean caseSensitive, boolean regexSearch)
838 throws ParseError {
839 return new SearchCompiler(caseSensitive, regexSearch,
840 new PushbackTokenizer(
841 new PushbackReader(new StringReader(searchStr))))
842 .parse();
843 }
844
845 public Match parse() throws ParseError {
846 Match m = parseExpression();
847 if (!tokenizer.readIfEqual(Token.EOF))
848 throw new ParseError(tr("Unexpected token: {0}", tokenizer.nextToken()));
849 if (m == null)
850 return new Always();
851 return m;
852 }
853
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);
866 }
867 }
868
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;
875 }
876
877 private Match parseFactor() throws ParseError {
878 if (tokenizer.readIfEqual(Token.LEFT_PARENT)) {
879 Match expression = parseExpression();
880 if (!tokenizer.readIfEqual(Token.RIGHT_PARENT))
881 throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken());
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))
888 return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber());
889 else if (tokenizer.readIfEqual(Token.COLON)) {
890 if ("id".equals(key))
891 return new Id(tokenizer.readNumber(tr("Primitive id expected")));
892 else if ("tags".equals(key)) {
893 Range range = tokenizer.readRange(tr("Range of numbers expected"));
894 return new TagCountRange(range.getStart(), range.getEnd());
895 } else if ("nodes".equals(key)) {
896 Range range = tokenizer.readRange(tr("Range of numbers expected"));
897 return new NodeCountRange(range.getStart(), range.getEnd());
898 } else if ("areasize".equals(key)) {
899 Range range = tokenizer.readRange(tr("Range of numbers expected"));
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 {
913 /* I18n: Don't translate timestamop keyword */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i>'' after ''timestamp''"));
914 }
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")));
919 else
920 return parseKV(key, tokenizer.readTextOrNumber());
921 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
922 return new BooleanMatch(key, false);
923 else if ("new".equals(key))
924 return new New();
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();
933 else if ("closed".equals(key))
934 return new Closed();
935 else if ("child".equals(key))
936 return new Child(parseFactor());
937 else if ("parent".equals(key))
938 return new Parent(parseFactor());
939 else if ("indownloadedarea".equals(key))
940 return new InDataSourceArea(false);
941 else if ("allindownloadedarea".equals(key))
942 return new InDataSourceArea(true);
943 else if ("inview".equals(key))
944 return new InView(false);
945 else if ("allinview".equals(key))
946 return new InView(true);
947 else
948 return new Any(key, regexSearch, caseSensitive);
949 } else
950 return null;
951 }
952
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;
959 }
960
961 private Match parseKV(String key, String value) throws ParseError {
962 if (value == null) {
963 value = "";
964 }
965 if (key.equals("type"))
966 return new ExactType(value);
967 else if (key.equals("user"))
968 return new UserMatch(value);
969 else if (key.equals("role"))
970 return new RoleMatch(value);
971 else
972 return new KeyValue(key, value, regexSearch, caseSensitive);
973 }
974
975 private static int regexFlags(boolean caseSensitive) {
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.
991 if (!caseSensitive) {
992 searchFlags |= (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
993 }
994
995 return searchFlags;
996 }
997}
Note: See TracBrowser for help on using the repository browser.