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

Last change on this file since 8896 was 8896, checked in by simon04, 9 years ago

see #10465 - SearchAction: drop isRole since role does the same

  • Property svn:eol-style set to native
File size: 52.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.Arrays;
11import java.util.Collection;
12import java.util.HashMap;
13import java.util.List;
14import java.util.Locale;
15import java.util.Map;
16import java.util.regex.Matcher;
17import java.util.regex.Pattern;
18import java.util.regex.PatternSyntaxException;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.actions.search.PushbackTokenizer.Range;
22import org.openstreetmap.josm.actions.search.PushbackTokenizer.Token;
23import org.openstreetmap.josm.data.Bounds;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
27import org.openstreetmap.josm.data.osm.OsmUtils;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.RelationMember;
30import org.openstreetmap.josm.data.osm.Way;
31import org.openstreetmap.josm.gui.mappaint.Environment;
32import org.openstreetmap.josm.gui.mappaint.mapcss.Selector;
33import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
34import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
35import org.openstreetmap.josm.tools.AlphanumComparator;
36import org.openstreetmap.josm.tools.Geometry;
37import org.openstreetmap.josm.tools.Predicate;
38import org.openstreetmap.josm.tools.Utils;
39import org.openstreetmap.josm.tools.date.DateUtils;
40
41/**
42 Implements a google-like search.
43 <br>
44 Grammar:
45<pre>
46expression =
47 fact | expression
48 fact expression
49 fact
50
51fact =
52 ( expression )
53 -fact
54 term?
55 term=term
56 term:term
57 term
58 </pre>
59
60 @author Imi
61 */
62public class SearchCompiler {
63
64 private boolean caseSensitive;
65 private boolean regexSearch;
66 private static String rxErrorMsg = marktr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}");
67 private static String rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}");
68 private PushbackTokenizer tokenizer;
69 private static Map<String, SimpleMatchFactory> simpleMatchFactoryMap = new HashMap<>();
70 private static Map<String, UnaryMatchFactory> unaryMatchFactoryMap = new HashMap<>();
71 private static Map<String, BinaryMatchFactory> binaryMatchFactoryMap = new HashMap<>();
72
73 public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) {
74 this.caseSensitive = caseSensitive;
75 this.regexSearch = regexSearch;
76 this.tokenizer = tokenizer;
77
78 /* register core match factories at first instance, so plugins should
79 * never be able to generate a NPE
80 */
81 if (simpleMatchFactoryMap.isEmpty()) {
82 addMatchFactory(new CoreSimpleMatchFactory());
83 }
84 if (unaryMatchFactoryMap.isEmpty()) {
85 addMatchFactory(new CoreUnaryMatchFactory());
86 }
87 }
88
89 /**
90 * Add (register) MatchFactory with SearchCompiler
91 * @param factory match factory
92 */
93 public static void addMatchFactory(MatchFactory factory) {
94 for (String keyword : factory.getKeywords()) {
95 // TODO: check for keyword collisions
96 if (factory instanceof SimpleMatchFactory) {
97 simpleMatchFactoryMap.put(keyword, (SimpleMatchFactory) factory);
98 } else if (factory instanceof UnaryMatchFactory) {
99 unaryMatchFactoryMap.put(keyword, (UnaryMatchFactory) factory);
100 } else if (factory instanceof BinaryMatchFactory) {
101 binaryMatchFactoryMap.put(keyword, (BinaryMatchFactory) factory);
102 } else
103 throw new AssertionError("Unknown match factory");
104 }
105 }
106
107 public class CoreSimpleMatchFactory implements SimpleMatchFactory {
108 private Collection<String> keywords = Arrays.asList("id", "version",
109 "changeset", "nodes", "ways", "tags", "areasize", "waylength", "modified", "selected",
110 "incomplete", "untagged", "closed", "new", "indownloadedarea",
111 "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole", "isRole");
112
113 @Override
114 public Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError {
115 switch(keyword) {
116 case "modified":
117 return new Modified();
118 case "selected":
119 return new Selected();
120 case "incomplete":
121 return new Incomplete();
122 case "untagged":
123 return new Untagged();
124 case "closed":
125 return new Closed();
126 case "new":
127 return new New();
128 case "indownloadedarea":
129 return new InDataSourceArea(false);
130 case "allindownloadedarea":
131 return new InDataSourceArea(true);
132 case "inview":
133 return new InView(false);
134 case "allinview":
135 return new InView(true);
136 default:
137 if (tokenizer != null) {
138 switch (keyword) {
139 case "id":
140 return new Id(tokenizer);
141 case "version":
142 return new Version(tokenizer);
143 case "changeset":
144 return new ChangesetId(tokenizer);
145 case "nodes":
146 return new NodeCountRange(tokenizer);
147 case "ways":
148 return new WayCountRange(tokenizer);
149 case "tags":
150 return new TagCountRange(tokenizer);
151 case "areasize":
152 return new AreaSize(tokenizer);
153 case "waylength":
154 return new WayLength(tokenizer);
155 case "nth":
156 return new Nth(tokenizer, false);
157 case "nth%":
158 return new Nth(tokenizer, true);
159 case "hasRole":
160 return new HasRole(tokenizer);
161 case "timestamp":
162 // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""})
163 String rangeS = ' ' + tokenizer.readTextOrNumber() + ' ';
164 String[] rangeA = rangeS.split("/");
165 if (rangeA.length == 1) {
166 return new KeyValue(keyword, rangeS.trim(), regexSearch, caseSensitive);
167 } else if (rangeA.length == 2) {
168 String rangeA1 = rangeA[0].trim();
169 String rangeA2 = rangeA[1].trim();
170 // if min timestap is empty: use lowest possible date
171 long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime();
172 // if max timestamp is empty: use "now"
173 long maxDate = rangeA2.isEmpty() ? System.currentTimeMillis() : DateUtils.fromString(rangeA2).getTime();
174 return new TimestampRange(minDate, maxDate);
175 } else {
176 // I18n: Don't translate timestamp keyword
177 throw new ParseError(tr("Expecting <i>min</i>/<i>max</i> after ''timestamp''"));
178 }
179 }
180 }
181 }
182 return null;
183 }
184
185 @Override
186 public Collection<String> getKeywords() {
187 return keywords;
188 }
189 }
190
191 public static class CoreUnaryMatchFactory implements UnaryMatchFactory {
192 private static Collection<String> keywords = Arrays.asList("parent", "child");
193
194 @Override
195 public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) {
196 if ("parent".equals(keyword))
197 return new Parent(matchOperand);
198 else if ("child".equals(keyword))
199 return new Child(matchOperand);
200 return null;
201 }
202
203 @Override
204 public Collection<String> getKeywords() {
205 return keywords;
206 }
207 }
208
209 /**
210 * Classes implementing this interface can provide Match operators.
211 */
212 private interface MatchFactory {
213 Collection<String> getKeywords();
214 }
215
216 public interface SimpleMatchFactory extends MatchFactory {
217 Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError;
218 }
219
220 public interface UnaryMatchFactory extends MatchFactory {
221 UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError;
222 }
223
224 public interface BinaryMatchFactory extends MatchFactory {
225 BinaryMatch get(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError;
226 }
227
228 /**
229 * Base class for all search operators.
230 */
231 public abstract static class Match implements Predicate<OsmPrimitive> {
232
233 public abstract boolean match(OsmPrimitive osm);
234
235 /**
236 * Tests whether one of the primitives matches.
237 */
238 protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) {
239 for (OsmPrimitive p : primitives) {
240 if (match(p))
241 return true;
242 }
243 return false;
244 }
245
246 /**
247 * Tests whether all primitives match.
248 */
249 protected boolean forallMatch(Collection<? extends OsmPrimitive> primitives) {
250 for (OsmPrimitive p : primitives) {
251 if (!match(p))
252 return false;
253 }
254 return true;
255 }
256
257 @Override
258 public final boolean evaluate(OsmPrimitive object) {
259 return match(object);
260 }
261 }
262
263 /**
264 * A unary search operator which may take data parameters.
265 */
266 public abstract static class UnaryMatch extends Match {
267
268 protected final Match match;
269
270 public UnaryMatch(Match match) {
271 if (match == null) {
272 // "operator" (null) should mean the same as "operator()"
273 // (Always). I.e. match everything
274 this.match = new Always();
275 } else {
276 this.match = match;
277 }
278 }
279
280 public Match getOperand() {
281 return match;
282 }
283 }
284
285 /**
286 * A binary search operator which may take data parameters.
287 */
288 public abstract static class BinaryMatch extends Match {
289
290 protected final Match lhs;
291 protected final Match rhs;
292
293 public BinaryMatch(Match lhs, Match rhs) {
294 this.lhs = lhs;
295 this.rhs = rhs;
296 }
297
298 public Match getLhs() {
299 return lhs;
300 }
301
302 public Match getRhs() {
303 return rhs;
304 }
305 }
306
307 /**
308 * Matches every OsmPrimitive.
309 */
310 public static class Always extends Match {
311 /** The unique instance/ */
312 public static final Always INSTANCE = new Always();
313 @Override
314 public boolean match(OsmPrimitive osm) {
315 return true;
316 }
317 }
318
319 /**
320 * Never matches any OsmPrimitive.
321 */
322 public static class Never extends Match {
323 @Override
324 public boolean match(OsmPrimitive osm) {
325 return false;
326 }
327 }
328
329 /**
330 * Inverts the match.
331 */
332 public static class Not extends UnaryMatch {
333 public Not(Match match) {
334 super(match);
335 }
336
337 @Override
338 public boolean match(OsmPrimitive osm) {
339 return !match.match(osm);
340 }
341
342 @Override
343 public String toString() {
344 return "!" + match;
345 }
346
347 public Match getMatch() {
348 return match;
349 }
350 }
351
352 /**
353 * Matches if the value of the corresponding key is ''yes'', ''true'', ''1'' or ''on''.
354 */
355 private static class BooleanMatch extends Match {
356 private final String key;
357 private final boolean defaultValue;
358
359 BooleanMatch(String key, boolean defaultValue) {
360 this.key = key;
361 this.defaultValue = defaultValue;
362 }
363
364 @Override
365 public boolean match(OsmPrimitive osm) {
366 Boolean ret = OsmUtils.getOsmBoolean(osm.get(key));
367 if (ret == null)
368 return defaultValue;
369 else
370 return ret;
371 }
372
373 @Override
374 public String toString() {
375 return key + '?';
376 }
377 }
378
379 /**
380 * Matches if both left and right expressions match.
381 */
382 public static class And extends BinaryMatch {
383 public And(Match lhs, Match rhs) {
384 super(lhs, rhs);
385 }
386
387 @Override
388 public boolean match(OsmPrimitive osm) {
389 return lhs.match(osm) && rhs.match(osm);
390 }
391
392 @Override
393 public String toString() {
394 return lhs + " && " + rhs;
395 }
396 }
397
398 /**
399 * Matches if the left OR the right expression match.
400 */
401 public static class Or extends BinaryMatch {
402 public Or(Match lhs, Match rhs) {
403 super(lhs, rhs);
404 }
405
406 @Override
407 public boolean match(OsmPrimitive osm) {
408 return lhs.match(osm) || rhs.match(osm);
409 }
410
411 @Override
412 public String toString() {
413 return lhs + " || " + rhs;
414 }
415 }
416
417 /**
418 * Matches if the left OR the right expression match, but not both.
419 */
420 public static class Xor extends BinaryMatch {
421 public Xor(Match lhs, Match rhs) {
422 super(lhs, rhs);
423 }
424
425 @Override
426 public boolean match(OsmPrimitive osm) {
427 return lhs.match(osm) ^ rhs.match(osm);
428 }
429
430 @Override
431 public String toString() {
432 return lhs + " ^ " + rhs;
433 }
434 }
435
436 /**
437 * Matches objects with ID in the given range.
438 */
439 private static class Id extends RangeMatch {
440 Id(Range range) {
441 super(range);
442 }
443
444 Id(PushbackTokenizer tokenizer) throws ParseError {
445 this(tokenizer.readRange(tr("Range of primitive ids expected")));
446 }
447
448 @Override
449 protected Long getNumber(OsmPrimitive osm) {
450 return osm.isNew() ? 0 : osm.getUniqueId();
451 }
452
453 @Override
454 protected String getString() {
455 return "id";
456 }
457 }
458
459 /**
460 * Matches objects with a changeset ID in the given range.
461 */
462 private static class ChangesetId extends RangeMatch {
463 ChangesetId(Range range) {
464 super(range);
465 }
466
467 ChangesetId(PushbackTokenizer tokenizer) throws ParseError {
468 this(tokenizer.readRange(tr("Range of changeset ids expected")));
469 }
470
471 @Override
472 protected Long getNumber(OsmPrimitive osm) {
473 return (long) osm.getChangesetId();
474 }
475
476 @Override
477 protected String getString() {
478 return "changeset";
479 }
480 }
481
482 /**
483 * Matches objects with a version number in the given range.
484 */
485 private static class Version extends RangeMatch {
486 Version(Range range) {
487 super(range);
488 }
489
490 Version(PushbackTokenizer tokenizer) throws ParseError {
491 this(tokenizer.readRange(tr("Range of versions expected")));
492 }
493
494 @Override
495 protected Long getNumber(OsmPrimitive osm) {
496 return (long) osm.getVersion();
497 }
498
499 @Override
500 protected String getString() {
501 return "version";
502 }
503 }
504
505 /**
506 * Matches objects with the given key-value pair.
507 */
508 private static class KeyValue extends Match {
509 private final String key;
510 private final Pattern keyPattern;
511 private final String value;
512 private final Pattern valuePattern;
513 private final boolean caseSensitive;
514
515 KeyValue(String key, String value, boolean regexSearch, boolean caseSensitive) throws ParseError {
516 this.caseSensitive = caseSensitive;
517 if (regexSearch) {
518 int searchFlags = regexFlags(caseSensitive);
519
520 try {
521 this.keyPattern = Pattern.compile(key, searchFlags);
522 } catch (PatternSyntaxException e) {
523 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
524 } catch (Exception e) {
525 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()), e);
526 }
527 try {
528 this.valuePattern = Pattern.compile(value, searchFlags);
529 } catch (PatternSyntaxException e) {
530 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
531 } catch (Exception e) {
532 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()), e);
533 }
534 this.key = key;
535 this.value = value;
536
537 } else if (caseSensitive) {
538 this.key = key;
539 this.value = value;
540 this.keyPattern = null;
541 this.valuePattern = null;
542 } else {
543 this.key = key;
544 this.value = value;
545 this.keyPattern = null;
546 this.valuePattern = null;
547 }
548 }
549
550 @Override
551 public boolean match(OsmPrimitive osm) {
552
553 if (keyPattern != null) {
554 if (!osm.hasKeys())
555 return false;
556
557 /* The string search will just get a key like
558 * 'highway' and look that up as osm.get(key). But
559 * since we're doing a regex match we'll have to loop
560 * over all the keys to see if they match our regex,
561 * and only then try to match against the value
562 */
563
564 for (String k: osm.keySet()) {
565 String v = osm.get(k);
566
567 Matcher matcherKey = keyPattern.matcher(k);
568 boolean matchedKey = matcherKey.find();
569
570 if (matchedKey) {
571 Matcher matcherValue = valuePattern.matcher(v);
572 boolean matchedValue = matcherValue.find();
573
574 if (matchedValue)
575 return true;
576 }
577 }
578 } else {
579 String mv = null;
580
581 if ("timestamp".equals(key)) {
582 mv = DateUtils.fromTimestamp(osm.getRawTimestamp());
583 } else {
584 mv = osm.get(key);
585 if (!caseSensitive && mv == null) {
586 for (String k: osm.keySet()) {
587 if (key.equalsIgnoreCase(k)) {
588 mv = osm.get(k);
589 break;
590 }
591 }
592 }
593 }
594
595 if (mv == null)
596 return false;
597
598 String v1 = caseSensitive ? mv : mv.toLowerCase(Locale.ENGLISH);
599 String v2 = caseSensitive ? value : value.toLowerCase(Locale.ENGLISH);
600
601 v1 = Normalizer.normalize(v1, Normalizer.Form.NFC);
602 v2 = Normalizer.normalize(v2, Normalizer.Form.NFC);
603 return v1.indexOf(v2) != -1;
604 }
605
606 return false;
607 }
608
609 @Override
610 public String toString() {
611 return key + '=' + value;
612 }
613 }
614
615 public static class ValueComparison extends Match {
616 private final String key;
617 private final String referenceValue;
618 private final Double referenceNumber;
619 private final int compareMode;
620 private static final Pattern ISO8601 = Pattern.compile("\\d+-\\d+-\\d+");
621
622 public ValueComparison(String key, String referenceValue, int compareMode) {
623 this.key = key;
624 this.referenceValue = referenceValue;
625 Double v = null;
626 try {
627 v = Double.valueOf(referenceValue);
628 } catch (NumberFormatException ignore) {
629 if (Main.isTraceEnabled()) {
630 Main.trace(ignore.getMessage());
631 }
632 }
633 this.referenceNumber = v;
634 this.compareMode = compareMode;
635 }
636
637 @Override
638 public boolean match(OsmPrimitive osm) {
639 final String currentValue = osm.get(key);
640 final int compareResult;
641 if (currentValue == null) {
642 return false;
643 } else if (ISO8601.matcher(currentValue).matches() || ISO8601.matcher(referenceValue).matches()) {
644 compareResult = currentValue.compareTo(referenceValue);
645 } else if (referenceNumber != null) {
646 try {
647 compareResult = Double.compare(Double.parseDouble(currentValue), referenceNumber);
648 } catch (NumberFormatException ignore) {
649 return false;
650 }
651 } else {
652 compareResult = AlphanumComparator.getInstance().compare(currentValue, referenceValue);
653 }
654 return compareMode < 0 ? compareResult < 0 : compareMode > 0 ? compareResult > 0 : compareResult == 0;
655 }
656
657 @Override
658 public String toString() {
659 return key + (compareMode == -1 ? "<" : compareMode == +1 ? ">" : "") + referenceValue;
660 }
661 }
662
663 /**
664 * Matches objects with the exact given key-value pair.
665 */
666 public static class ExactKeyValue extends Match {
667
668 private enum Mode {
669 ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY,
670 ANY_KEY_REGEXP, ANY_VALUE_REGEXP, EXACT_REGEXP, MISSING_KEY_REGEXP;
671 }
672
673 private final String key;
674 private final String value;
675 private final Pattern keyPattern;
676 private final Pattern valuePattern;
677 private final Mode mode;
678
679 public ExactKeyValue(boolean regexp, String key, String value) throws ParseError {
680 if ("".equals(key))
681 throw new ParseError(tr("Key cannot be empty when tag operator is used. Sample use: key=value"));
682 this.key = key;
683 this.value = value == null ? "" : value;
684 if ("".equals(this.value) && "*".equals(key)) {
685 mode = Mode.NONE;
686 } else if ("".equals(this.value)) {
687 if (regexp) {
688 mode = Mode.MISSING_KEY_REGEXP;
689 } else {
690 mode = Mode.MISSING_KEY;
691 }
692 } else if ("*".equals(key) && "*".equals(this.value)) {
693 mode = Mode.ANY;
694 } else if ("*".equals(key)) {
695 if (regexp) {
696 mode = Mode.ANY_KEY_REGEXP;
697 } else {
698 mode = Mode.ANY_KEY;
699 }
700 } else if ("*".equals(this.value)) {
701 if (regexp) {
702 mode = Mode.ANY_VALUE_REGEXP;
703 } else {
704 mode = Mode.ANY_VALUE;
705 }
706 } else {
707 if (regexp) {
708 mode = Mode.EXACT_REGEXP;
709 } else {
710 mode = Mode.EXACT;
711 }
712 }
713
714 if (regexp && !key.isEmpty() && !"*".equals(key)) {
715 try {
716 keyPattern = Pattern.compile(key, regexFlags(false));
717 } catch (PatternSyntaxException e) {
718 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
719 } catch (Exception e) {
720 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()), e);
721 }
722 } else {
723 keyPattern = null;
724 }
725 if (regexp && !this.value.isEmpty() && !"*".equals(this.value)) {
726 try {
727 valuePattern = Pattern.compile(this.value, regexFlags(false));
728 } catch (PatternSyntaxException e) {
729 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
730 } catch (Exception e) {
731 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()), e);
732 }
733 } else {
734 valuePattern = null;
735 }
736 }
737
738 @Override
739 public boolean match(OsmPrimitive osm) {
740
741 if (!osm.hasKeys())
742 return mode == Mode.NONE;
743
744 switch (mode) {
745 case NONE:
746 return false;
747 case MISSING_KEY:
748 return osm.get(key) == null;
749 case ANY:
750 return true;
751 case ANY_VALUE:
752 return osm.get(key) != null;
753 case ANY_KEY:
754 for (String v:osm.getKeys().values()) {
755 if (v.equals(value))
756 return true;
757 }
758 return false;
759 case EXACT:
760 return value.equals(osm.get(key));
761 case ANY_KEY_REGEXP:
762 for (String v:osm.getKeys().values()) {
763 if (valuePattern.matcher(v).matches())
764 return true;
765 }
766 return false;
767 case ANY_VALUE_REGEXP:
768 case EXACT_REGEXP:
769 for (String key: osm.keySet()) {
770 if (keyPattern.matcher(key).matches()) {
771 if (mode == Mode.ANY_VALUE_REGEXP
772 || valuePattern.matcher(osm.get(key)).matches())
773 return true;
774 }
775 }
776 return false;
777 case MISSING_KEY_REGEXP:
778 for (String k:osm.keySet()) {
779 if (keyPattern.matcher(k).matches())
780 return false;
781 }
782 return true;
783 }
784 throw new AssertionError("Missed state");
785 }
786
787 @Override
788 public String toString() {
789 return key + '=' + value;
790 }
791 }
792
793 /**
794 * Match a string in any tags (key or value), with optional regex and case insensitivity.
795 */
796 private static class Any extends Match {
797 private final String search;
798 private final Pattern searchRegex;
799 private final boolean caseSensitive;
800
801 Any(String s, boolean regexSearch, boolean caseSensitive) throws ParseError {
802 s = Normalizer.normalize(s, Normalizer.Form.NFC);
803 this.caseSensitive = caseSensitive;
804 if (regexSearch) {
805 try {
806 this.searchRegex = Pattern.compile(s, regexFlags(caseSensitive));
807 } catch (PatternSyntaxException e) {
808 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
809 } catch (Exception e) {
810 throw new ParseError(tr(rxErrorMsgNoPos, s, e.getMessage()), e);
811 }
812 this.search = s;
813 } else if (caseSensitive) {
814 this.search = s;
815 this.searchRegex = null;
816 } else {
817 this.search = s.toLowerCase(Locale.ENGLISH);
818 this.searchRegex = null;
819 }
820 }
821
822 @Override
823 public boolean match(OsmPrimitive osm) {
824 if (!osm.hasKeys() && osm.getUser() == null)
825 return search.isEmpty();
826
827 for (String key: osm.keySet()) {
828 String value = osm.get(key);
829 if (searchRegex != null) {
830
831 value = Normalizer.normalize(value, Normalizer.Form.NFC);
832
833 Matcher keyMatcher = searchRegex.matcher(key);
834 Matcher valMatcher = searchRegex.matcher(value);
835
836 boolean keyMatchFound = keyMatcher.find();
837 boolean valMatchFound = valMatcher.find();
838
839 if (keyMatchFound || valMatchFound)
840 return true;
841 } else {
842 if (!caseSensitive) {
843 key = key.toLowerCase(Locale.ENGLISH);
844 value = value.toLowerCase(Locale.ENGLISH);
845 }
846
847 value = Normalizer.normalize(value, Normalizer.Form.NFC);
848
849 if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
850 return true;
851 }
852 }
853 return false;
854 }
855
856 @Override
857 public String toString() {
858 return search;
859 }
860 }
861
862 private static class ExactType extends Match {
863 private final OsmPrimitiveType type;
864
865 ExactType(String type) throws ParseError {
866 this.type = OsmPrimitiveType.from(type);
867 if (this.type == null)
868 throw new ParseError(tr("Unknown primitive type: {0}. Allowed values are node, way or relation",
869 type));
870 }
871
872 @Override
873 public boolean match(OsmPrimitive osm) {
874 return type.equals(osm.getType());
875 }
876
877 @Override
878 public String toString() {
879 return "type=" + type;
880 }
881 }
882
883 /**
884 * Matches objects last changed by the given username.
885 */
886 private static class UserMatch extends Match {
887 private String user;
888
889 UserMatch(String user) {
890 if ("anonymous".equals(user)) {
891 this.user = null;
892 } else {
893 this.user = user;
894 }
895 }
896
897 @Override
898 public boolean match(OsmPrimitive osm) {
899 if (osm.getUser() == null)
900 return user == null;
901 else
902 return osm.getUser().hasName(user);
903 }
904
905 @Override
906 public String toString() {
907 return "user=" + (user == null ? "" : user);
908 }
909 }
910
911 /**
912 * Matches objects with the given relation role (i.e. "outer").
913 */
914 private static class RoleMatch extends Match {
915 private String role;
916
917 RoleMatch(String role) {
918 if (role == null) {
919 this.role = "";
920 } else {
921 this.role = role;
922 }
923 }
924
925 @Override
926 public boolean match(OsmPrimitive osm) {
927 for (OsmPrimitive ref: osm.getReferrers()) {
928 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
929 for (RelationMember m : ((Relation) ref).getMembers()) {
930 if (m.getMember() == osm) {
931 String testRole = m.getRole();
932 if (role.equals(testRole == null ? "" : testRole))
933 return true;
934 }
935 }
936 }
937 }
938 return false;
939 }
940
941 @Override
942 public String toString() {
943 return "role=" + role;
944 }
945 }
946
947 /**
948 * Matches the n-th object of a relation and/or the n-th node of a way.
949 */
950 private static class Nth extends Match {
951
952 private final int nth;
953 private final boolean modulo;
954
955 Nth(PushbackTokenizer tokenizer, boolean modulo) throws ParseError {
956 this((int) tokenizer.readNumber(tr("Positive integer expected")), modulo);
957 }
958
959 private Nth(int nth, boolean modulo) throws ParseError {
960 this.nth = nth;
961 this.modulo = modulo;
962 }
963
964 @Override
965 public boolean match(OsmPrimitive osm) {
966 for (OsmPrimitive p : osm.getReferrers()) {
967 final int idx;
968 final int maxIndex;
969 if (p instanceof Way) {
970 Way w = (Way) p;
971 idx = w.getNodes().indexOf(osm);
972 maxIndex = w.getNodesCount();
973 } else if (p instanceof Relation) {
974 Relation r = (Relation) p;
975 idx = r.getMemberPrimitivesList().indexOf(osm);
976 maxIndex = r.getMembersCount();
977 } else {
978 continue;
979 }
980 if (nth < 0 && idx - maxIndex == nth) {
981 return true;
982 } else if (idx == nth || (modulo && idx % nth == 0))
983 return true;
984 }
985 return false;
986 }
987
988 @Override
989 public String toString() {
990 return "Nth{nth=" + nth + ", modulo=" + modulo + '}';
991 }
992 }
993
994 /**
995 * Matches objects with properties in a certain range.
996 */
997 private abstract static class RangeMatch extends Match {
998
999 private final long min;
1000 private final long max;
1001
1002 RangeMatch(long min, long max) {
1003 this.min = Math.min(min, max);
1004 this.max = Math.max(min, max);
1005 }
1006
1007 RangeMatch(Range range) {
1008 this(range.getStart(), range.getEnd());
1009 }
1010
1011 protected abstract Long getNumber(OsmPrimitive osm);
1012
1013 protected abstract String getString();
1014
1015 @Override
1016 public boolean match(OsmPrimitive osm) {
1017 Long num = getNumber(osm);
1018 if (num == null)
1019 return false;
1020 else
1021 return (num >= min) && (num <= max);
1022 }
1023
1024 @Override
1025 public String toString() {
1026 return getString() + '=' + min + '-' + max;
1027 }
1028 }
1029
1030 /**
1031 * Matches ways with a number of nodes in given range
1032 */
1033 private static class NodeCountRange extends RangeMatch {
1034 NodeCountRange(Range range) {
1035 super(range);
1036 }
1037
1038 NodeCountRange(PushbackTokenizer tokenizer) throws ParseError {
1039 this(tokenizer.readRange(tr("Range of numbers expected")));
1040 }
1041
1042 @Override
1043 protected Long getNumber(OsmPrimitive osm) {
1044 if (osm instanceof Way) {
1045 return (long) ((Way) osm).getRealNodesCount();
1046 } else if (osm instanceof Relation) {
1047 return (long) ((Relation) osm).getMemberPrimitives(Node.class).size();
1048 } else {
1049 return null;
1050 }
1051 }
1052
1053 @Override
1054 protected String getString() {
1055 return "nodes";
1056 }
1057 }
1058
1059 /**
1060 * Matches objects with the number of referring/contained ways in the given range
1061 */
1062 private static class WayCountRange extends RangeMatch {
1063 WayCountRange(Range range) {
1064 super(range);
1065 }
1066
1067 WayCountRange(PushbackTokenizer tokenizer) throws ParseError {
1068 this(tokenizer.readRange(tr("Range of numbers expected")));
1069 }
1070
1071 @Override
1072 protected Long getNumber(OsmPrimitive osm) {
1073 if (osm instanceof Node) {
1074 return (long) Utils.filteredCollection(osm.getReferrers(), Way.class).size();
1075 } else if (osm instanceof Relation) {
1076 return (long) ((Relation) osm).getMemberPrimitives(Way.class).size();
1077 } else {
1078 return null;
1079 }
1080 }
1081
1082 @Override
1083 protected String getString() {
1084 return "ways";
1085 }
1086 }
1087
1088 /**
1089 * Matches objects with a number of tags in given range
1090 */
1091 private static class TagCountRange extends RangeMatch {
1092 TagCountRange(Range range) {
1093 super(range);
1094 }
1095
1096 TagCountRange(PushbackTokenizer tokenizer) throws ParseError {
1097 this(tokenizer.readRange(tr("Range of numbers expected")));
1098 }
1099
1100 @Override
1101 protected Long getNumber(OsmPrimitive osm) {
1102 return (long) osm.getKeys().size();
1103 }
1104
1105 @Override
1106 protected String getString() {
1107 return "tags";
1108 }
1109 }
1110
1111 /**
1112 * Matches objects with a timestamp in given range
1113 */
1114 private static class TimestampRange extends RangeMatch {
1115
1116 TimestampRange(long minCount, long maxCount) {
1117 super(minCount, maxCount);
1118 }
1119
1120 @Override
1121 protected Long getNumber(OsmPrimitive osm) {
1122 return osm.getRawTimestamp() * 1000L;
1123 }
1124
1125 @Override
1126 protected String getString() {
1127 return "timestamp";
1128 }
1129 }
1130
1131 /**
1132 * Matches relations with a member of the given role
1133 */
1134 private static class HasRole extends Match {
1135 private final String role;
1136
1137 HasRole(PushbackTokenizer tokenizer) {
1138 role = tokenizer.readTextOrNumber();
1139 }
1140
1141 @Override
1142 public boolean match(OsmPrimitive osm) {
1143 return osm instanceof Relation && ((Relation) osm).getMemberRoles().contains(role);
1144 }
1145 }
1146
1147 /**
1148 * Matches objects that are new (i.e. have not been uploaded to the server)
1149 */
1150 private static class New extends Match {
1151 @Override
1152 public boolean match(OsmPrimitive osm) {
1153 return osm.isNew();
1154 }
1155
1156 @Override
1157 public String toString() {
1158 return "new";
1159 }
1160 }
1161
1162 /**
1163 * Matches all objects that have been modified, created, or undeleted
1164 */
1165 private static class Modified extends Match {
1166 @Override
1167 public boolean match(OsmPrimitive osm) {
1168 return osm.isModified() || osm.isNewOrUndeleted();
1169 }
1170
1171 @Override
1172 public String toString() {
1173 return "modified";
1174 }
1175 }
1176
1177 /**
1178 * Matches all objects currently selected
1179 */
1180 private static class Selected extends Match {
1181 @Override
1182 public boolean match(OsmPrimitive osm) {
1183 return osm.getDataSet().isSelected(osm);
1184 }
1185
1186 @Override
1187 public String toString() {
1188 return "selected";
1189 }
1190 }
1191
1192 /**
1193 * Match objects that are incomplete, where only id and type are known.
1194 * Typically some members of a relation are incomplete until they are
1195 * fetched from the server.
1196 */
1197 private static class Incomplete extends Match {
1198 @Override
1199 public boolean match(OsmPrimitive osm) {
1200 return osm.isIncomplete();
1201 }
1202
1203 @Override
1204 public String toString() {
1205 return "incomplete";
1206 }
1207 }
1208
1209 /**
1210 * Matches objects that don't have any interesting tags (i.e. only has source,
1211 * FIXME, etc.). The complete list of uninteresting tags can be found here:
1212 * org.openstreetmap.josm.data.osm.OsmPrimitive.getUninterestingKeys()
1213 */
1214 private static class Untagged extends Match {
1215 @Override
1216 public boolean match(OsmPrimitive osm) {
1217 return !osm.isTagged() && !osm.isIncomplete();
1218 }
1219
1220 @Override
1221 public String toString() {
1222 return "untagged";
1223 }
1224 }
1225
1226 /**
1227 * Matches ways which are closed (i.e. first and last node are the same)
1228 */
1229 private static class Closed extends Match {
1230 @Override
1231 public boolean match(OsmPrimitive osm) {
1232 return osm instanceof Way && ((Way) osm).isClosed();
1233 }
1234
1235 @Override
1236 public String toString() {
1237 return "closed";
1238 }
1239 }
1240
1241 /**
1242 * Matches objects if they are parents of the expression
1243 */
1244 public static class Parent extends UnaryMatch {
1245 public Parent(Match m) {
1246 super(m);
1247 }
1248
1249 @Override
1250 public boolean match(OsmPrimitive osm) {
1251 boolean isParent = false;
1252
1253 if (osm instanceof Way) {
1254 for (Node n : ((Way) osm).getNodes()) {
1255 isParent |= match.match(n);
1256 }
1257 } else if (osm instanceof Relation) {
1258 for (RelationMember member : ((Relation) osm).getMembers()) {
1259 isParent |= match.match(member.getMember());
1260 }
1261 }
1262 return isParent;
1263 }
1264
1265 @Override
1266 public String toString() {
1267 return "parent(" + match + ')';
1268 }
1269 }
1270
1271 /**
1272 * Matches objects if they are children of the expression
1273 */
1274 public static class Child extends UnaryMatch {
1275
1276 public Child(Match m) {
1277 super(m);
1278 }
1279
1280 @Override
1281 public boolean match(OsmPrimitive osm) {
1282 boolean isChild = false;
1283 for (OsmPrimitive p : osm.getReferrers()) {
1284 isChild |= match.match(p);
1285 }
1286 return isChild;
1287 }
1288
1289 @Override
1290 public String toString() {
1291 return "child(" + match + ')';
1292 }
1293 }
1294
1295 /**
1296 * Matches if the size of the area is within the given range
1297 *
1298 * @author Ole Jørgen Brønner
1299 */
1300 private static class AreaSize extends RangeMatch {
1301
1302 AreaSize(Range range) {
1303 super(range);
1304 }
1305
1306 AreaSize(PushbackTokenizer tokenizer) throws ParseError {
1307 this(tokenizer.readRange(tr("Range of numbers expected")));
1308 }
1309
1310 @Override
1311 protected Long getNumber(OsmPrimitive osm) {
1312 if (!(osm instanceof Way && ((Way) osm).isClosed()))
1313 return null;
1314 Way way = (Way) osm;
1315 return (long) Geometry.closedWayArea(way);
1316 }
1317
1318 @Override
1319 protected String getString() {
1320 return "areasize";
1321 }
1322 }
1323
1324 /**
1325 * Matches if the length of a way is within the given range
1326 */
1327 private static class WayLength extends RangeMatch {
1328
1329 WayLength(Range range) {
1330 super(range);
1331 }
1332
1333 WayLength(PushbackTokenizer tokenizer) throws ParseError {
1334 this(tokenizer.readRange(tr("Range of numbers expected")));
1335 }
1336
1337 @Override
1338 protected Long getNumber(OsmPrimitive osm) {
1339 if (!(osm instanceof Way))
1340 return null;
1341 Way way = (Way) osm;
1342 return (long) way.getLength();
1343 }
1344
1345 @Override
1346 protected String getString() {
1347 return "waylength";
1348 }
1349 }
1350
1351 /**
1352 * Matches objects within the given bounds.
1353 */
1354 private abstract static class InArea extends Match {
1355
1356 protected final boolean all;
1357
1358 /**
1359 * @param all if true, all way nodes or relation members have to be within source area;if false, one suffices.
1360 */
1361 InArea(boolean all) {
1362 this.all = all;
1363 }
1364
1365 protected abstract Bounds getBounds();
1366
1367 @Override
1368 public boolean match(OsmPrimitive osm) {
1369 if (!osm.isUsable())
1370 return false;
1371 else if (osm instanceof Node) {
1372 Bounds bounds = getBounds();
1373 return bounds != null && bounds.contains(((Node) osm).getCoor());
1374 } else if (osm instanceof Way) {
1375 Collection<Node> nodes = ((Way) osm).getNodes();
1376 return all ? forallMatch(nodes) : existsMatch(nodes);
1377 } else if (osm instanceof Relation) {
1378 Collection<OsmPrimitive> primitives = ((Relation) osm).getMemberPrimitives();
1379 return all ? forallMatch(primitives) : existsMatch(primitives);
1380 } else
1381 return false;
1382 }
1383 }
1384
1385 /**
1386 * Matches objects within source area ("downloaded area").
1387 */
1388 public static class InDataSourceArea extends InArea {
1389
1390 public InDataSourceArea(boolean all) {
1391 super(all);
1392 }
1393
1394 @Override
1395 protected Bounds getBounds() {
1396 return Main.main.getCurrentDataSet() == null || Main.main.getCurrentDataSet().getDataSourceArea() == null
1397 ? null : new Bounds(Main.main.getCurrentDataSet().getDataSourceArea().getBounds2D());
1398 }
1399
1400 @Override
1401 public String toString() {
1402 return all ? "allindownloadedarea" : "indownloadedarea";
1403 }
1404 }
1405
1406 /**
1407 * Matches objects within current map view.
1408 */
1409 private static class InView extends InArea {
1410
1411 InView(boolean all) {
1412 super(all);
1413 }
1414
1415 @Override
1416 protected Bounds getBounds() {
1417 if (!Main.isDisplayingMapView()) {
1418 return null;
1419 }
1420 return Main.map.mapView.getRealBounds();
1421 }
1422
1423 @Override
1424 public String toString() {
1425 return all ? "allinview" : "inview";
1426 }
1427 }
1428
1429 public static class ParseError extends Exception {
1430 public ParseError(String msg) {
1431 super(msg);
1432 }
1433
1434 public ParseError(String msg, Throwable cause) {
1435 super(msg, cause);
1436 }
1437
1438 public ParseError(Token expected, Token found) {
1439 this(tr("Unexpected token. Expected {0}, found {1}", expected, found));
1440 }
1441 }
1442
1443 /**
1444 * Compiles the search expression.
1445 * @param searchStr the search expression
1446 * @return a {@link Match} object for the expression
1447 * @throws ParseError if an error has been encountered while compiling
1448 * @see #compile(org.openstreetmap.josm.actions.search.SearchAction.SearchSetting)
1449 */
1450 public static Match compile(String searchStr) throws ParseError {
1451 return new SearchCompiler(false, false,
1452 new PushbackTokenizer(
1453 new PushbackReader(new StringReader(searchStr))))
1454 .parse();
1455 }
1456
1457 /**
1458 * Compiles the search expression.
1459 * @param setting the settings to use
1460 * @return a {@link Match} object for the expression
1461 * @throws ParseError if an error has been encountered while compiling
1462 * @see #compile(String)
1463 */
1464 public static Match compile(SearchAction.SearchSetting setting) throws ParseError {
1465 if (setting.mapCSSSearch) {
1466 return compileMapCSS(setting.text);
1467 }
1468 return new SearchCompiler(setting.caseSensitive, setting.regexSearch,
1469 new PushbackTokenizer(
1470 new PushbackReader(new StringReader(setting.text))))
1471 .parse();
1472 }
1473
1474 static Match compileMapCSS(String mapCSS) throws ParseError {
1475 try {
1476 final List<Selector> selectors = new MapCSSParser(new StringReader(mapCSS)).selectors();
1477 return new Match() {
1478 @Override
1479 public boolean match(OsmPrimitive osm) {
1480 for (Selector selector : selectors) {
1481 if (selector.matches(new Environment(osm))) {
1482 return true;
1483 }
1484 }
1485 return false;
1486 }
1487 };
1488 } catch (ParseException e) {
1489 throw new ParseError(tr("Failed to parse MapCSS selector"), e);
1490 }
1491 }
1492
1493 /**
1494 * Parse search string.
1495 *
1496 * @return match determined by search string
1497 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1498 */
1499 public Match parse() throws ParseError {
1500 Match m = parseExpression();
1501 if (!tokenizer.readIfEqual(Token.EOF))
1502 throw new ParseError(tr("Unexpected token: {0}", tokenizer.nextToken()));
1503 if (m == null)
1504 m = new Always();
1505 Main.debug("Parsed search expression is {0}", m);
1506 return m;
1507 }
1508
1509 /**
1510 * Parse expression. This is a recursive method.
1511 *
1512 * @return match determined by parsing expression
1513 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1514 */
1515 private Match parseExpression() throws ParseError {
1516 Match factor = parseFactor();
1517 if (factor == null)
1518 // empty search string
1519 return null;
1520 if (tokenizer.readIfEqual(Token.OR))
1521 return new Or(factor, parseExpression(tr("Missing parameter for OR")));
1522 else if (tokenizer.readIfEqual(Token.XOR))
1523 return new Xor(factor, parseExpression(tr("Missing parameter for XOR")));
1524 else {
1525 Match expression = parseExpression();
1526 if (expression == null)
1527 // reached end of search string, no more recursive calls
1528 return factor;
1529 else
1530 // the default operator is AND
1531 return new And(factor, expression);
1532 }
1533 }
1534
1535 /**
1536 * Parse expression, showing the specified error message if parsing fails.
1537 *
1538 * @param errorMessage to display if parsing error occurs
1539 * @return match determined by parsing expression
1540 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1541 * @see #parseExpression()
1542 */
1543 private Match parseExpression(String errorMessage) throws ParseError {
1544 Match expression = parseExpression();
1545 if (expression == null)
1546 throw new ParseError(errorMessage);
1547 else
1548 return expression;
1549 }
1550
1551 /**
1552 * Parse next factor (a search operator or search term).
1553 *
1554 * @return match determined by parsing factor string
1555 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1556 */
1557 private Match parseFactor() throws ParseError {
1558 if (tokenizer.readIfEqual(Token.LEFT_PARENT)) {
1559 Match expression = parseExpression();
1560 if (!tokenizer.readIfEqual(Token.RIGHT_PARENT))
1561 throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken());
1562 return expression;
1563 } else if (tokenizer.readIfEqual(Token.NOT)) {
1564 return new Not(parseFactor(tr("Missing operator for NOT")));
1565 } else if (tokenizer.readIfEqual(Token.KEY)) {
1566 // factor consists of key:value or key=value
1567 String key = tokenizer.getText();
1568 if (tokenizer.readIfEqual(Token.EQUALS)) {
1569 return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber());
1570 } else if (tokenizer.readIfEqual(Token.LESS_THAN)) {
1571 return new ValueComparison(key, tokenizer.readTextOrNumber(), -1);
1572 } else if (tokenizer.readIfEqual(Token.GREATER_THAN)) {
1573 return new ValueComparison(key, tokenizer.readTextOrNumber(), +1);
1574 } else if (tokenizer.readIfEqual(Token.COLON)) {
1575 // see if we have a Match that takes a data parameter
1576 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
1577 if (factory != null)
1578 return factory.get(key, tokenizer);
1579
1580 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
1581 if (unaryFactory != null)
1582 return unaryFactory.get(key, parseFactor(), tokenizer);
1583
1584 // key:value form where value is a string (may be OSM key search)
1585 return parseKV(key, tokenizer.readTextOrNumber());
1586 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
1587 return new BooleanMatch(key, false);
1588 else {
1589 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
1590 if (factory != null)
1591 return factory.get(key, null);
1592
1593 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
1594 if (unaryFactory != null)
1595 return unaryFactory.get(key, parseFactor(), null);
1596
1597 // match string in any key or value
1598 return new Any(key, regexSearch, caseSensitive);
1599 }
1600 } else
1601 return null;
1602 }
1603
1604 private Match parseFactor(String errorMessage) throws ParseError {
1605 Match fact = parseFactor();
1606 if (fact == null)
1607 throw new ParseError(errorMessage);
1608 else
1609 return fact;
1610 }
1611
1612 private Match parseKV(String key, String value) throws ParseError {
1613 if (value == null) {
1614 value = "";
1615 }
1616 switch(key) {
1617 case "type":
1618 return new ExactType(value);
1619 case "user":
1620 return new UserMatch(value);
1621 case "role":
1622 return new RoleMatch(value);
1623 default:
1624 return new KeyValue(key, value, regexSearch, caseSensitive);
1625 }
1626 }
1627
1628 private static int regexFlags(boolean caseSensitive) {
1629 int searchFlags = 0;
1630
1631 // Enables canonical Unicode equivalence so that e.g. the two
1632 // forms of "\u00e9gal" and "e\u0301gal" will match.
1633 //
1634 // It makes sense to match no matter how the character
1635 // happened to be constructed.
1636 searchFlags |= Pattern.CANON_EQ;
1637
1638 // Make "." match any character including newline (/s in Perl)
1639 searchFlags |= Pattern.DOTALL;
1640
1641 // CASE_INSENSITIVE by itself only matches US-ASCII case
1642 // insensitively, but the OSM data is in Unicode. With
1643 // UNICODE_CASE casefolding is made Unicode-aware.
1644 if (!caseSensitive) {
1645 searchFlags |= (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
1646 }
1647
1648 return searchFlags;
1649 }
1650}
Note: See TracBrowser for help on using the repository browser.