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

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

javadoc fixes

  • Property svn:eol-style set to native
File size: 52.8 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 v = Double.valueOf(referenceValue);
638 } catch (NumberFormatException ignore) {
639 if (Main.isTraceEnabled()) {
640 Main.trace(ignore.getMessage());
641 }
642 }
643 this.referenceNumber = v;
644 this.compareMode = compareMode;
645 }
646
647 @Override
648 public boolean match(OsmPrimitive osm) {
649 final String currentValue = osm.get(key);
650 final int compareResult;
651 if (currentValue == null) {
652 return false;
653 } else if (ISO8601.matcher(currentValue).matches() || ISO8601.matcher(referenceValue).matches()) {
654 compareResult = currentValue.compareTo(referenceValue);
655 } else if (referenceNumber != null) {
656 try {
657 compareResult = Double.compare(Double.parseDouble(currentValue), referenceNumber);
658 } catch (NumberFormatException ignore) {
659 return false;
660 }
661 } else {
662 compareResult = AlphanumComparator.getInstance().compare(currentValue, referenceValue);
663 }
664 return compareMode < 0 ? compareResult < 0 : compareMode > 0 ? compareResult > 0 : compareResult == 0;
665 }
666
667 @Override
668 public String toString() {
669 return key + (compareMode == -1 ? "<" : compareMode == +1 ? ">" : "") + referenceValue;
670 }
671 }
672
673 /**
674 * Matches objects with the exact given key-value pair.
675 */
676 public static class ExactKeyValue extends Match {
677
678 private enum Mode {
679 ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY,
680 ANY_KEY_REGEXP, ANY_VALUE_REGEXP, EXACT_REGEXP, MISSING_KEY_REGEXP;
681 }
682
683 private final String key;
684 private final String value;
685 private final Pattern keyPattern;
686 private final Pattern valuePattern;
687 private final Mode mode;
688
689 public ExactKeyValue(boolean regexp, String key, String value) throws ParseError {
690 if ("".equals(key))
691 throw new ParseError(tr("Key cannot be empty when tag operator is used. Sample use: key=value"));
692 this.key = key;
693 this.value = value == null ? "" : value;
694 if ("".equals(this.value) && "*".equals(key)) {
695 mode = Mode.NONE;
696 } else if ("".equals(this.value)) {
697 if (regexp) {
698 mode = Mode.MISSING_KEY_REGEXP;
699 } else {
700 mode = Mode.MISSING_KEY;
701 }
702 } else if ("*".equals(key) && "*".equals(this.value)) {
703 mode = Mode.ANY;
704 } else if ("*".equals(key)) {
705 if (regexp) {
706 mode = Mode.ANY_KEY_REGEXP;
707 } else {
708 mode = Mode.ANY_KEY;
709 }
710 } else if ("*".equals(this.value)) {
711 if (regexp) {
712 mode = Mode.ANY_VALUE_REGEXP;
713 } else {
714 mode = Mode.ANY_VALUE;
715 }
716 } else {
717 if (regexp) {
718 mode = Mode.EXACT_REGEXP;
719 } else {
720 mode = Mode.EXACT;
721 }
722 }
723
724 if (regexp && !key.isEmpty() && !"*".equals(key)) {
725 try {
726 keyPattern = Pattern.compile(key, regexFlags(false));
727 } catch (PatternSyntaxException e) {
728 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
729 } catch (Exception e) {
730 throw new ParseError(tr(rxErrorMsgNoPos, key, e.getMessage()), e);
731 }
732 } else {
733 keyPattern = null;
734 }
735 if (regexp && !this.value.isEmpty() && !"*".equals(this.value)) {
736 try {
737 valuePattern = Pattern.compile(this.value, regexFlags(false));
738 } catch (PatternSyntaxException e) {
739 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
740 } catch (Exception e) {
741 throw new ParseError(tr(rxErrorMsgNoPos, value, e.getMessage()), e);
742 }
743 } else {
744 valuePattern = null;
745 }
746 }
747
748 @Override
749 public boolean match(OsmPrimitive osm) {
750
751 if (!osm.hasKeys())
752 return mode == Mode.NONE;
753
754 switch (mode) {
755 case NONE:
756 return false;
757 case MISSING_KEY:
758 return osm.get(key) == null;
759 case ANY:
760 return true;
761 case ANY_VALUE:
762 return osm.get(key) != null;
763 case ANY_KEY:
764 for (String v:osm.getKeys().values()) {
765 if (v.equals(value))
766 return true;
767 }
768 return false;
769 case EXACT:
770 return value.equals(osm.get(key));
771 case ANY_KEY_REGEXP:
772 for (String v:osm.getKeys().values()) {
773 if (valuePattern.matcher(v).matches())
774 return true;
775 }
776 return false;
777 case ANY_VALUE_REGEXP:
778 case EXACT_REGEXP:
779 for (String key: osm.keySet()) {
780 if (keyPattern.matcher(key).matches()) {
781 if (mode == Mode.ANY_VALUE_REGEXP
782 || valuePattern.matcher(osm.get(key)).matches())
783 return true;
784 }
785 }
786 return false;
787 case MISSING_KEY_REGEXP:
788 for (String k:osm.keySet()) {
789 if (keyPattern.matcher(k).matches())
790 return false;
791 }
792 return true;
793 }
794 throw new AssertionError("Missed state");
795 }
796
797 @Override
798 public String toString() {
799 return key + '=' + value;
800 }
801 }
802
803 /**
804 * Match a string in any tags (key or value), with optional regex and case insensitivity.
805 */
806 private static class Any extends Match {
807 private final String search;
808 private final Pattern searchRegex;
809 private final boolean caseSensitive;
810
811 Any(String s, boolean regexSearch, boolean caseSensitive) throws ParseError {
812 s = Normalizer.normalize(s, Normalizer.Form.NFC);
813 this.caseSensitive = caseSensitive;
814 if (regexSearch) {
815 try {
816 this.searchRegex = Pattern.compile(s, regexFlags(caseSensitive));
817 } catch (PatternSyntaxException e) {
818 throw new ParseError(tr(rxErrorMsg, e.getPattern(), e.getIndex(), e.getMessage()), e);
819 } catch (Exception e) {
820 throw new ParseError(tr(rxErrorMsgNoPos, s, e.getMessage()), e);
821 }
822 this.search = s;
823 } else if (caseSensitive) {
824 this.search = s;
825 this.searchRegex = null;
826 } else {
827 this.search = s.toLowerCase(Locale.ENGLISH);
828 this.searchRegex = null;
829 }
830 }
831
832 @Override
833 public boolean match(OsmPrimitive osm) {
834 if (!osm.hasKeys() && osm.getUser() == null)
835 return search.isEmpty();
836
837 for (String key: osm.keySet()) {
838 String value = osm.get(key);
839 if (searchRegex != null) {
840
841 value = Normalizer.normalize(value, Normalizer.Form.NFC);
842
843 Matcher keyMatcher = searchRegex.matcher(key);
844 Matcher valMatcher = searchRegex.matcher(value);
845
846 boolean keyMatchFound = keyMatcher.find();
847 boolean valMatchFound = valMatcher.find();
848
849 if (keyMatchFound || valMatchFound)
850 return true;
851 } else {
852 if (!caseSensitive) {
853 key = key.toLowerCase(Locale.ENGLISH);
854 value = value.toLowerCase(Locale.ENGLISH);
855 }
856
857 value = Normalizer.normalize(value, Normalizer.Form.NFC);
858
859 if (key.indexOf(search) != -1 || value.indexOf(search) != -1)
860 return true;
861 }
862 }
863 return false;
864 }
865
866 @Override
867 public String toString() {
868 return search;
869 }
870 }
871
872 private static class ExactType extends Match {
873 private final OsmPrimitiveType type;
874
875 ExactType(String type) throws ParseError {
876 this.type = OsmPrimitiveType.from(type);
877 if (this.type == null)
878 throw new ParseError(tr("Unknown primitive type: {0}. Allowed values are node, way or relation",
879 type));
880 }
881
882 @Override
883 public boolean match(OsmPrimitive osm) {
884 return type.equals(osm.getType());
885 }
886
887 @Override
888 public String toString() {
889 return "type=" + type;
890 }
891 }
892
893 /**
894 * Matches objects last changed by the given username.
895 */
896 private static class UserMatch extends Match {
897 private String user;
898
899 UserMatch(String user) {
900 if ("anonymous".equals(user)) {
901 this.user = null;
902 } else {
903 this.user = user;
904 }
905 }
906
907 @Override
908 public boolean match(OsmPrimitive osm) {
909 if (osm.getUser() == null)
910 return user == null;
911 else
912 return osm.getUser().hasName(user);
913 }
914
915 @Override
916 public String toString() {
917 return "user=" + (user == null ? "" : user);
918 }
919 }
920
921 /**
922 * Matches objects with the given relation role (i.e. "outer").
923 */
924 private static class RoleMatch extends Match {
925 private String role;
926
927 RoleMatch(String role) {
928 if (role == null) {
929 this.role = "";
930 } else {
931 this.role = role;
932 }
933 }
934
935 @Override
936 public boolean match(OsmPrimitive osm) {
937 for (OsmPrimitive ref: osm.getReferrers()) {
938 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
939 for (RelationMember m : ((Relation) ref).getMembers()) {
940 if (m.getMember() == osm) {
941 String testRole = m.getRole();
942 if (role.equals(testRole == null ? "" : testRole))
943 return true;
944 }
945 }
946 }
947 }
948 return false;
949 }
950
951 @Override
952 public String toString() {
953 return "role=" + role;
954 }
955 }
956
957 /**
958 * Matches the n-th object of a relation and/or the n-th node of a way.
959 */
960 private static class Nth extends Match {
961
962 private final int nth;
963 private final boolean modulo;
964
965 Nth(PushbackTokenizer tokenizer, boolean modulo) throws ParseError {
966 this((int) tokenizer.readNumber(tr("Positive integer expected")), modulo);
967 }
968
969 private Nth(int nth, boolean modulo) throws ParseError {
970 this.nth = nth;
971 this.modulo = modulo;
972 }
973
974 @Override
975 public boolean match(OsmPrimitive osm) {
976 for (OsmPrimitive p : osm.getReferrers()) {
977 final int idx;
978 final int maxIndex;
979 if (p instanceof Way) {
980 Way w = (Way) p;
981 idx = w.getNodes().indexOf(osm);
982 maxIndex = w.getNodesCount();
983 } else if (p instanceof Relation) {
984 Relation r = (Relation) p;
985 idx = r.getMemberPrimitivesList().indexOf(osm);
986 maxIndex = r.getMembersCount();
987 } else {
988 continue;
989 }
990 if (nth < 0 && idx - maxIndex == nth) {
991 return true;
992 } else if (idx == nth || (modulo && idx % nth == 0))
993 return true;
994 }
995 return false;
996 }
997
998 @Override
999 public String toString() {
1000 return "Nth{nth=" + nth + ", modulo=" + modulo + '}';
1001 }
1002 }
1003
1004 /**
1005 * Matches objects with properties in a certain range.
1006 */
1007 private abstract static class RangeMatch extends Match {
1008
1009 private final long min;
1010 private final long max;
1011
1012 RangeMatch(long min, long max) {
1013 this.min = Math.min(min, max);
1014 this.max = Math.max(min, max);
1015 }
1016
1017 RangeMatch(Range range) {
1018 this(range.getStart(), range.getEnd());
1019 }
1020
1021 protected abstract Long getNumber(OsmPrimitive osm);
1022
1023 protected abstract String getString();
1024
1025 @Override
1026 public boolean match(OsmPrimitive osm) {
1027 Long num = getNumber(osm);
1028 if (num == null)
1029 return false;
1030 else
1031 return (num >= min) && (num <= max);
1032 }
1033
1034 @Override
1035 public String toString() {
1036 return getString() + '=' + min + '-' + max;
1037 }
1038 }
1039
1040 /**
1041 * Matches ways with a number of nodes in given range
1042 */
1043 private static class NodeCountRange extends RangeMatch {
1044 NodeCountRange(Range range) {
1045 super(range);
1046 }
1047
1048 NodeCountRange(PushbackTokenizer tokenizer) throws ParseError {
1049 this(tokenizer.readRange(tr("Range of numbers expected")));
1050 }
1051
1052 @Override
1053 protected Long getNumber(OsmPrimitive osm) {
1054 if (osm instanceof Way) {
1055 return (long) ((Way) osm).getRealNodesCount();
1056 } else if (osm instanceof Relation) {
1057 return (long) ((Relation) osm).getMemberPrimitives(Node.class).size();
1058 } else {
1059 return null;
1060 }
1061 }
1062
1063 @Override
1064 protected String getString() {
1065 return "nodes";
1066 }
1067 }
1068
1069 /**
1070 * Matches objects with the number of referring/contained ways in the given range
1071 */
1072 private static class WayCountRange extends RangeMatch {
1073 WayCountRange(Range range) {
1074 super(range);
1075 }
1076
1077 WayCountRange(PushbackTokenizer tokenizer) throws ParseError {
1078 this(tokenizer.readRange(tr("Range of numbers expected")));
1079 }
1080
1081 @Override
1082 protected Long getNumber(OsmPrimitive osm) {
1083 if (osm instanceof Node) {
1084 return (long) Utils.filteredCollection(osm.getReferrers(), Way.class).size();
1085 } else if (osm instanceof Relation) {
1086 return (long) ((Relation) osm).getMemberPrimitives(Way.class).size();
1087 } else {
1088 return null;
1089 }
1090 }
1091
1092 @Override
1093 protected String getString() {
1094 return "ways";
1095 }
1096 }
1097
1098 /**
1099 * Matches objects with a number of tags in given range
1100 */
1101 private static class TagCountRange extends RangeMatch {
1102 TagCountRange(Range range) {
1103 super(range);
1104 }
1105
1106 TagCountRange(PushbackTokenizer tokenizer) throws ParseError {
1107 this(tokenizer.readRange(tr("Range of numbers expected")));
1108 }
1109
1110 @Override
1111 protected Long getNumber(OsmPrimitive osm) {
1112 return (long) osm.getKeys().size();
1113 }
1114
1115 @Override
1116 protected String getString() {
1117 return "tags";
1118 }
1119 }
1120
1121 /**
1122 * Matches objects with a timestamp in given range
1123 */
1124 private static class TimestampRange extends RangeMatch {
1125
1126 TimestampRange(long minCount, long maxCount) {
1127 super(minCount, maxCount);
1128 }
1129
1130 @Override
1131 protected Long getNumber(OsmPrimitive osm) {
1132 return osm.getRawTimestamp() * 1000L;
1133 }
1134
1135 @Override
1136 protected String getString() {
1137 return "timestamp";
1138 }
1139 }
1140
1141 /**
1142 * Matches relations with a member of the given role
1143 */
1144 private static class HasRole extends Match {
1145 private final String role;
1146
1147 HasRole(PushbackTokenizer tokenizer) {
1148 role = tokenizer.readTextOrNumber();
1149 }
1150
1151 @Override
1152 public boolean match(OsmPrimitive osm) {
1153 return osm instanceof Relation && ((Relation) osm).getMemberRoles().contains(role);
1154 }
1155 }
1156
1157 /**
1158 * Matches objects that are new (i.e. have not been uploaded to the server)
1159 */
1160 private static class New extends Match {
1161 @Override
1162 public boolean match(OsmPrimitive osm) {
1163 return osm.isNew();
1164 }
1165
1166 @Override
1167 public String toString() {
1168 return "new";
1169 }
1170 }
1171
1172 /**
1173 * Matches all objects that have been modified, created, or undeleted
1174 */
1175 private static class Modified extends Match {
1176 @Override
1177 public boolean match(OsmPrimitive osm) {
1178 return osm.isModified() || osm.isNewOrUndeleted();
1179 }
1180
1181 @Override
1182 public String toString() {
1183 return "modified";
1184 }
1185 }
1186
1187 /**
1188 * Matches all objects currently selected
1189 */
1190 private static class Selected extends Match {
1191 @Override
1192 public boolean match(OsmPrimitive osm) {
1193 return osm.getDataSet().isSelected(osm);
1194 }
1195
1196 @Override
1197 public String toString() {
1198 return "selected";
1199 }
1200 }
1201
1202 /**
1203 * Match objects that are incomplete, where only id and type are known.
1204 * Typically some members of a relation are incomplete until they are
1205 * fetched from the server.
1206 */
1207 private static class Incomplete extends Match {
1208 @Override
1209 public boolean match(OsmPrimitive osm) {
1210 return osm.isIncomplete();
1211 }
1212
1213 @Override
1214 public String toString() {
1215 return "incomplete";
1216 }
1217 }
1218
1219 /**
1220 * Matches objects that don't have any interesting tags (i.e. only has source,
1221 * FIXME, etc.). The complete list of uninteresting tags can be found here:
1222 * org.openstreetmap.josm.data.osm.OsmPrimitive.getUninterestingKeys()
1223 */
1224 private static class Untagged extends Match {
1225 @Override
1226 public boolean match(OsmPrimitive osm) {
1227 return !osm.isTagged() && !osm.isIncomplete();
1228 }
1229
1230 @Override
1231 public String toString() {
1232 return "untagged";
1233 }
1234 }
1235
1236 /**
1237 * Matches ways which are closed (i.e. first and last node are the same)
1238 */
1239 private static class Closed extends Match {
1240 @Override
1241 public boolean match(OsmPrimitive osm) {
1242 return osm instanceof Way && ((Way) osm).isClosed();
1243 }
1244
1245 @Override
1246 public String toString() {
1247 return "closed";
1248 }
1249 }
1250
1251 /**
1252 * Matches objects if they are parents of the expression
1253 */
1254 public static class Parent extends UnaryMatch {
1255 public Parent(Match m) {
1256 super(m);
1257 }
1258
1259 @Override
1260 public boolean match(OsmPrimitive osm) {
1261 boolean isParent = false;
1262
1263 if (osm instanceof Way) {
1264 for (Node n : ((Way) osm).getNodes()) {
1265 isParent |= match.match(n);
1266 }
1267 } else if (osm instanceof Relation) {
1268 for (RelationMember member : ((Relation) osm).getMembers()) {
1269 isParent |= match.match(member.getMember());
1270 }
1271 }
1272 return isParent;
1273 }
1274
1275 @Override
1276 public String toString() {
1277 return "parent(" + match + ')';
1278 }
1279 }
1280
1281 /**
1282 * Matches objects if they are children of the expression
1283 */
1284 public static class Child extends UnaryMatch {
1285
1286 public Child(Match m) {
1287 super(m);
1288 }
1289
1290 @Override
1291 public boolean match(OsmPrimitive osm) {
1292 boolean isChild = false;
1293 for (OsmPrimitive p : osm.getReferrers()) {
1294 isChild |= match.match(p);
1295 }
1296 return isChild;
1297 }
1298
1299 @Override
1300 public String toString() {
1301 return "child(" + match + ')';
1302 }
1303 }
1304
1305 /**
1306 * Matches if the size of the area is within the given range
1307 *
1308 * @author Ole Jørgen Brønner
1309 */
1310 private static class AreaSize extends RangeMatch {
1311
1312 AreaSize(Range range) {
1313 super(range);
1314 }
1315
1316 AreaSize(PushbackTokenizer tokenizer) throws ParseError {
1317 this(tokenizer.readRange(tr("Range of numbers expected")));
1318 }
1319
1320 @Override
1321 protected Long getNumber(OsmPrimitive osm) {
1322 if (!(osm instanceof Way && ((Way) osm).isClosed()))
1323 return null;
1324 Way way = (Way) osm;
1325 return (long) Geometry.closedWayArea(way);
1326 }
1327
1328 @Override
1329 protected String getString() {
1330 return "areasize";
1331 }
1332 }
1333
1334 /**
1335 * Matches if the length of a way is within the given range
1336 */
1337 private static class WayLength extends RangeMatch {
1338
1339 WayLength(Range range) {
1340 super(range);
1341 }
1342
1343 WayLength(PushbackTokenizer tokenizer) throws ParseError {
1344 this(tokenizer.readRange(tr("Range of numbers expected")));
1345 }
1346
1347 @Override
1348 protected Long getNumber(OsmPrimitive osm) {
1349 if (!(osm instanceof Way))
1350 return null;
1351 Way way = (Way) osm;
1352 return (long) way.getLength();
1353 }
1354
1355 @Override
1356 protected String getString() {
1357 return "waylength";
1358 }
1359 }
1360
1361 /**
1362 * Matches objects within the given bounds.
1363 */
1364 private abstract static class InArea extends Match {
1365
1366 protected final boolean all;
1367
1368 /**
1369 * @param all if true, all way nodes or relation members have to be within source area;if false, one suffices.
1370 */
1371 InArea(boolean all) {
1372 this.all = all;
1373 }
1374
1375 protected abstract Bounds getBounds();
1376
1377 @Override
1378 public boolean match(OsmPrimitive osm) {
1379 if (!osm.isUsable())
1380 return false;
1381 else if (osm instanceof Node) {
1382 Bounds bounds = getBounds();
1383 return bounds != null && bounds.contains(((Node) osm).getCoor());
1384 } else if (osm instanceof Way) {
1385 Collection<Node> nodes = ((Way) osm).getNodes();
1386 return all ? forallMatch(nodes) : existsMatch(nodes);
1387 } else if (osm instanceof Relation) {
1388 Collection<OsmPrimitive> primitives = ((Relation) osm).getMemberPrimitives();
1389 return all ? forallMatch(primitives) : existsMatch(primitives);
1390 } else
1391 return false;
1392 }
1393 }
1394
1395 /**
1396 * Matches objects within source area ("downloaded area").
1397 */
1398 public static class InDataSourceArea extends InArea {
1399
1400 public InDataSourceArea(boolean all) {
1401 super(all);
1402 }
1403
1404 @Override
1405 protected Bounds getBounds() {
1406 return Main.main.getCurrentDataSet() == null || Main.main.getCurrentDataSet().getDataSourceArea() == null
1407 ? null : new Bounds(Main.main.getCurrentDataSet().getDataSourceArea().getBounds2D());
1408 }
1409
1410 @Override
1411 public String toString() {
1412 return all ? "allindownloadedarea" : "indownloadedarea";
1413 }
1414 }
1415
1416 /**
1417 * Matches objects within current map view.
1418 */
1419 private static class InView extends InArea {
1420
1421 InView(boolean all) {
1422 super(all);
1423 }
1424
1425 @Override
1426 protected Bounds getBounds() {
1427 if (!Main.isDisplayingMapView()) {
1428 return null;
1429 }
1430 return Main.map.mapView.getRealBounds();
1431 }
1432
1433 @Override
1434 public String toString() {
1435 return all ? "allinview" : "inview";
1436 }
1437 }
1438
1439 public static class ParseError extends Exception {
1440 public ParseError(String msg) {
1441 super(msg);
1442 }
1443
1444 public ParseError(String msg, Throwable cause) {
1445 super(msg, cause);
1446 }
1447
1448 public ParseError(Token expected, Token found) {
1449 this(tr("Unexpected token. Expected {0}, found {1}", expected, found));
1450 }
1451 }
1452
1453 /**
1454 * Compiles the search expression.
1455 * @param searchStr the search expression
1456 * @return a {@link Match} object for the expression
1457 * @throws ParseError if an error has been encountered while compiling
1458 * @see #compile(org.openstreetmap.josm.actions.search.SearchAction.SearchSetting)
1459 */
1460 public static Match compile(String searchStr) throws ParseError {
1461 return new SearchCompiler(false, false,
1462 new PushbackTokenizer(
1463 new PushbackReader(new StringReader(searchStr))))
1464 .parse();
1465 }
1466
1467 /**
1468 * Compiles the search expression.
1469 * @param setting the settings to use
1470 * @return a {@link Match} object for the expression
1471 * @throws ParseError if an error has been encountered while compiling
1472 * @see #compile(String)
1473 */
1474 public static Match compile(SearchAction.SearchSetting setting) throws ParseError {
1475 if (setting.mapCSSSearch) {
1476 return compileMapCSS(setting.text);
1477 }
1478 return new SearchCompiler(setting.caseSensitive, setting.regexSearch,
1479 new PushbackTokenizer(
1480 new PushbackReader(new StringReader(setting.text))))
1481 .parse();
1482 }
1483
1484 static Match compileMapCSS(String mapCSS) throws ParseError {
1485 try {
1486 final List<Selector> selectors = new MapCSSParser(new StringReader(mapCSS)).selectors();
1487 return new Match() {
1488 @Override
1489 public boolean match(OsmPrimitive osm) {
1490 for (Selector selector : selectors) {
1491 if (selector.matches(new Environment(osm))) {
1492 return true;
1493 }
1494 }
1495 return false;
1496 }
1497 };
1498 } catch (ParseException e) {
1499 throw new ParseError(tr("Failed to parse MapCSS selector"), e);
1500 }
1501 }
1502
1503 /**
1504 * Parse search string.
1505 *
1506 * @return match determined by search string
1507 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1508 */
1509 public Match parse() throws ParseError {
1510 Match m = parseExpression();
1511 if (!tokenizer.readIfEqual(Token.EOF))
1512 throw new ParseError(tr("Unexpected token: {0}", tokenizer.nextToken()));
1513 if (m == null)
1514 m = new Always();
1515 Main.debug("Parsed search expression is {0}", m);
1516 return m;
1517 }
1518
1519 /**
1520 * Parse expression. This is a recursive method.
1521 *
1522 * @return match determined by parsing expression
1523 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1524 */
1525 private Match parseExpression() throws ParseError {
1526 Match factor = parseFactor();
1527 if (factor == null)
1528 // empty search string
1529 return null;
1530 if (tokenizer.readIfEqual(Token.OR))
1531 return new Or(factor, parseExpression(tr("Missing parameter for OR")));
1532 else if (tokenizer.readIfEqual(Token.XOR))
1533 return new Xor(factor, parseExpression(tr("Missing parameter for XOR")));
1534 else {
1535 Match expression = parseExpression();
1536 if (expression == null)
1537 // reached end of search string, no more recursive calls
1538 return factor;
1539 else
1540 // the default operator is AND
1541 return new And(factor, expression);
1542 }
1543 }
1544
1545 /**
1546 * Parse expression, showing the specified error message if parsing fails.
1547 *
1548 * @param errorMessage to display if parsing error occurs
1549 * @return match determined by parsing expression
1550 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1551 * @see #parseExpression()
1552 */
1553 private Match parseExpression(String errorMessage) throws ParseError {
1554 Match expression = parseExpression();
1555 if (expression == null)
1556 throw new ParseError(errorMessage);
1557 else
1558 return expression;
1559 }
1560
1561 /**
1562 * Parse next factor (a search operator or search term).
1563 *
1564 * @return match determined by parsing factor string
1565 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError if search expression cannot be parsed
1566 */
1567 private Match parseFactor() throws ParseError {
1568 if (tokenizer.readIfEqual(Token.LEFT_PARENT)) {
1569 Match expression = parseExpression();
1570 if (!tokenizer.readIfEqual(Token.RIGHT_PARENT))
1571 throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken());
1572 return expression;
1573 } else if (tokenizer.readIfEqual(Token.NOT)) {
1574 return new Not(parseFactor(tr("Missing operator for NOT")));
1575 } else if (tokenizer.readIfEqual(Token.KEY)) {
1576 // factor consists of key:value or key=value
1577 String key = tokenizer.getText();
1578 if (tokenizer.readIfEqual(Token.EQUALS)) {
1579 return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber());
1580 } else if (tokenizer.readIfEqual(Token.LESS_THAN)) {
1581 return new ValueComparison(key, tokenizer.readTextOrNumber(), -1);
1582 } else if (tokenizer.readIfEqual(Token.GREATER_THAN)) {
1583 return new ValueComparison(key, tokenizer.readTextOrNumber(), +1);
1584 } else if (tokenizer.readIfEqual(Token.COLON)) {
1585 // see if we have a Match that takes a data parameter
1586 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
1587 if (factory != null)
1588 return factory.get(key, tokenizer);
1589
1590 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
1591 if (unaryFactory != null)
1592 return unaryFactory.get(key, parseFactor(), tokenizer);
1593
1594 // key:value form where value is a string (may be OSM key search)
1595 final String value = tokenizer.readTextOrNumber();
1596 return new KeyValue(key, value != null ? value : "", regexSearch, caseSensitive);
1597 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK))
1598 return new BooleanMatch(key, false);
1599 else {
1600 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key);
1601 if (factory != null)
1602 return factory.get(key, null);
1603
1604 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key);
1605 if (unaryFactory != null)
1606 return unaryFactory.get(key, parseFactor(), null);
1607
1608 // match string in any key or value
1609 return new Any(key, regexSearch, caseSensitive);
1610 }
1611 } else
1612 return null;
1613 }
1614
1615 private Match parseFactor(String errorMessage) throws ParseError {
1616 Match fact = parseFactor();
1617 if (fact == null)
1618 throw new ParseError(errorMessage);
1619 else
1620 return fact;
1621 }
1622
1623 private static int regexFlags(boolean caseSensitive) {
1624 int searchFlags = 0;
1625
1626 // Enables canonical Unicode equivalence so that e.g. the two
1627 // forms of "\u00e9gal" and "e\u0301gal" will match.
1628 //
1629 // It makes sense to match no matter how the character
1630 // happened to be constructed.
1631 searchFlags |= Pattern.CANON_EQ;
1632
1633 // Make "." match any character including newline (/s in Perl)
1634 searchFlags |= Pattern.DOTALL;
1635
1636 // CASE_INSENSITIVE by itself only matches US-ASCII case
1637 // insensitively, but the OSM data is in Unicode. With
1638 // UNICODE_CASE casefolding is made Unicode-aware.
1639 if (!caseSensitive) {
1640 searchFlags |= (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
1641 }
1642
1643 return searchFlags;
1644 }
1645}
Note: See TracBrowser for help on using the repository browser.