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

Last change on this file since 3317 was 3317, checked in by stoecker, 14 years ago

allow to search incomplete elements again

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