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

Last change on this file since 4546 was 4546, checked in by jttt, 13 years ago

Extend name templates with context switch - possibility to use tags of referenced primitive when constructing primitive name

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