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

Last change on this file since 8971 was 8971, checked in by Don-vip, 9 years ago

Search dialog: provide real-time visual feedback on valid/invalid search expressions typed by user

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