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

Last change on this file since 3924 was 3818, checked in by bastiK, 13 years ago

fixed #5857 - add "closed" to search, fixed #5015 - logical AND & for search tool

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