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

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

Refactoring in SearchCompiler: parse type, user, role as any other keyword

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