Ticket #7178: factory_plugin_search_operators.patch
File factory_plugin_search_operators.patch, 35.8 KB (added by , 12 years ago) |
---|
-
src/org/openstreetmap/josm/actions/search/PushbackTokenizer.java
47 47 } 48 48 49 49 public enum Token { 50 NOT(marktr("<not>")), OR(marktr("<or>")), LEFT_PARENT(marktr("<left parent>")),50 NOT(marktr("<not>")), OR(marktr("<or>")), XOR(marktr("<xor>")), LEFT_PARENT(marktr("<left parent>")), 51 51 RIGHT_PARENT(marktr("<right parent>")), COLON(marktr("<colon>")), EQUALS(marktr("<equals>")), 52 52 KEY(marktr("<key>")), QUESTION_MARK(marktr("<question mark>")), 53 53 EOF(marktr("<end-of-file>")); … … 73 73 } 74 74 } 75 75 76 private static final List<Character> specialChars = Arrays.asList(new Character[] {'"', ':', '(', ')', '|', ' =', '?'});76 private static final List<Character> specialChars = Arrays.asList(new Character[] {'"', ':', '(', ')', '|', '^', '=', '?'}); 77 77 private static final List<Character> specialCharsQuoted = Arrays.asList(new Character[] {'"'}); 78 78 79 79 private String getString(boolean quoted) { … … 101 101 * - for an '-'. This will be the only character 102 102 * : for an key. The value is the next token 103 103 * | for "OR" 104 * ^ for "XOR" 104 105 * ' ' for anything else. 105 106 * @return The next token in the stream. 106 107 */ … … 133 134 case '|': 134 135 getChar(); 135 136 return Token.OR; 137 case '^': 138 getChar(); 139 return Token.XOR; 136 140 case '&': 137 141 getChar(); 138 142 return nextToken(); … … 155 159 currentText = prefix + getString(); 156 160 if ("or".equalsIgnoreCase(currentText)) 157 161 return Token.OR; 158 if ("and".equalsIgnoreCase(currentText)) 162 else if ("xor".equalsIgnoreCase(currentText)) 163 return Token.XOR; 164 else if ("and".equalsIgnoreCase(currentText)) 159 165 return nextToken(); 160 166 // try parsing number 161 167 try { -
src/org/openstreetmap/josm/actions/search/SearchCompiler.java
9 9 import java.text.Normalizer; 10 10 import java.util.Collection; 11 11 import java.util.Date; 12 import java.util.Vector; 12 13 import java.util.regex.Matcher; 13 14 import java.util.regex.Pattern; 14 15 import java.util.regex.PatternSyntaxException; … … 54 55 private static String rxErrorMsg = marktr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}"); 55 56 private static String rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}"); 56 57 private PushbackTokenizer tokenizer; 58 private static MasterMatchFactory matchFactory = null; 57 59 58 60 public SearchCompiler(boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) { 59 61 this.caseSensitive = caseSensitive; 60 62 this.regexSearch = regexSearch; 61 63 this.tokenizer = tokenizer; 64 65 /* register core match factory at first instance, so plugins should 66 * never be able to generate a NPE 67 */ 68 if (matchFactory == null) { 69 matchFactory = new MasterMatchFactory(); 70 matchFactory.add(new CoreMatchFactory()); 71 } 72 62 73 } 74 75 /** 76 * Add (register) MatchFactory with SearchCompiler 77 * @param factory 78 */ 79 public static void addMatchFactory(MatchFactory factory) { 80 matchFactory.add(factory); 81 } 82 83 public static class MasterMatchFactory implements MatchFactory { 63 84 85 private static Collection<MatchFactory> matchFactories = new Vector<MatchFactory>(); 86 87 public void add(MatchFactory factory) { 88 matchFactories.add(factory); 89 } 90 91 @Override 92 public Match getSimpleMatch(String keyword, PushbackTokenizer tokenizer) throws ParseError { 93 for (MatchFactory factory : matchFactories) { 94 Match match = factory.getSimpleMatch(keyword, tokenizer); 95 if (match != null) { 96 return match; 97 } 98 } 99 return null; 100 } 101 102 @Override 103 public UnaryMatch getUnaryMatch(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError { 104 for (MatchFactory factory : matchFactories) { 105 UnaryMatch match = factory.getUnaryMatch(keyword, matchOperand, tokenizer); 106 if (match != null) { 107 return match; 108 } 109 } 110 return null; 111 } 112 113 @Override 114 public BinaryMatch getBinaryMatch(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError { 115 for (MatchFactory factory : matchFactories) { 116 BinaryMatch match = factory.getBinaryMatch(keyword, lhs, rhs, tokenizer); 117 if (match != null) { 118 return match; 119 } 120 } 121 return null; 122 } 123 124 @Override 125 public Collection<String> getKeywords() { 126 throw new UnsupportedOperationException("Not supported yet."); 127 } 128 129 @Override 130 public boolean isUnaryMatch(String keyword) { 131 for (MatchFactory factory : matchFactories) { 132 if (factory.isUnaryMatch(keyword)) { 133 return true; 134 } 135 } 136 return false; 137 } 138 139 @Override 140 public boolean isBinaryMatch(String keyword) { 141 for (MatchFactory factory : matchFactories) { 142 if (factory.isBinaryMatch(keyword)) { 143 return true; 144 } 145 } 146 return false; 147 } 148 } 149 150 public class CoreMatchFactory implements MatchFactory { 151 152 @Override 153 public Match getSimpleMatch(String keyword, PushbackTokenizer tokenizer) throws ParseError { 154 if ("id".equals(keyword)) { 155 return new Id(tokenizer); 156 } else if ("version".equals(keyword)) { 157 return new Version(tokenizer); 158 } else if ("changeset".equals(keyword)) { 159 return new ChangesetId(tokenizer); 160 } else if ("nodes".equals(keyword)) { 161 return new NodeCountRange(tokenizer); 162 } else if ("tags".equals(keyword)) { 163 return new TagCountRange(tokenizer); 164 } else if ("areasize".equals(keyword)) { 165 return new AreaSize(tokenizer); 166 } else if ("modified".equals(keyword)) { 167 return new Modified(); 168 } else if ("selected".equals(keyword)) { 169 return new Selected(); 170 } else if ("incomplete".equals(keyword)) { 171 return new Incomplete(); 172 } else if ("untagged".equals(keyword)) { 173 return new Untagged(); 174 } else if ("closed".equals(keyword)) { 175 return new Closed(); 176 } else if ("new".equals(keyword)) { 177 return new New(); 178 } else if ("indownloadedarea".equals(keyword)) { 179 return new InDataSourceArea(false); 180 } else if ("allindownloadedarea".equals(keyword)) { 181 return new InDataSourceArea(true); 182 } else if ("inview".equals(keyword)) { 183 return new InView(false); 184 } else if ("allinview".equals(keyword)) { 185 return new InView(true); 186 } else if ("timestamp".equals(keyword)) { 187 // TODO: how to handle this? symptom of inadequate search grammar? 188 String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 189 String[] rangeA = rangeS.split("/"); 190 if (rangeA.length == 1) { 191 return new KeyValue(keyword, rangeS, regexSearch, caseSensitive); 192 } else if (rangeA.length == 2) { 193 String rangeA1 = rangeA[0].trim(); 194 String rangeA2 = rangeA[1].trim(); 195 long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date 196 long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now" 197 return new TimestampRange(minDate, maxDate); 198 } else { 199 /* 200 * I18n: Don't translate timestamp keyword 201 */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i> after ''timestamp''")); 202 } 203 } else { 204 return null; 205 } 206 } 207 208 @Override 209 public UnaryMatch getUnaryMatch(String keyword, Match matchOperand, PushbackTokenizer tokenizer) { 210 if ("parent".equals(keyword)) { 211 return new Parent(matchOperand); 212 } else if ("child".equals(keyword)) { 213 return new Child(matchOperand); 214 } 215 return null; 216 } 217 218 @Override 219 public BinaryMatch getBinaryMatch(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) { 220 throw new UnsupportedOperationException("Not supported yet."); 221 } 222 223 @Override 224 public Collection<String> getKeywords() { 225 throw new UnsupportedOperationException("Not supported yet."); 226 } 227 228 @Override 229 public boolean isUnaryMatch(String keyword) { 230 if ("parent".equals(keyword) || "child".equals(keyword)) { 231 return true; 232 } else { 233 return false; 234 } 235 } 236 237 @Override 238 public boolean isBinaryMatch(String keyword) { 239 return false; 240 } 241 } 242 243 /** 244 * Classes implementing this interface can provide Match operators. 245 */ 246 public interface MatchFactory { 247 248 public Match getSimpleMatch(String keyword, PushbackTokenizer tokenizer) throws ParseError; 249 250 public UnaryMatch getUnaryMatch(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError; 251 252 public BinaryMatch getBinaryMatch(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError; 253 254 public Collection<String> getKeywords(); 255 256 public boolean isUnaryMatch(String keyword); 257 258 public boolean isBinaryMatch(String keyword); 259 } 260 261 /** 262 * Base class for all search operators. 263 */ 64 264 abstract public static class Match { 265 65 266 abstract public boolean match(OsmPrimitive osm); 66 267 67 268 /** … … 69 270 */ 70 271 protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) { 71 272 for (OsmPrimitive p : primitives) { 72 if (match(p)) 273 if (match(p)) { 73 274 return true; 275 } 74 276 } 75 277 return false; 76 278 } … … 80 282 */ 81 283 protected boolean forallMatch(Collection<? extends OsmPrimitive> primitives) { 82 284 for (OsmPrimitive p : primitives) { 83 if (!match(p)) 285 if (!match(p)) { 84 286 return false; 287 } 85 288 } 86 289 return true; 87 290 } 88 291 } 89 292 293 /** 294 * A unary search operator which may take data parameters. 295 */ 296 abstract public static class UnaryMatch extends Match { 297 298 protected final Match match; 299 300 public UnaryMatch(Match match) { 301 if (match == null) { 302 // "operator" (null) should mean the same as "operator()" 303 // (Always). I.e. match everything 304 this.match = new Always(); 305 } else { 306 this.match = match; 307 } 308 } 309 310 public Match getOperand() { 311 return match; 312 } 313 } 314 315 /** 316 * A binary search operator which may take data parameters. 317 */ 318 abstract public static class BinaryMatch extends Match { 319 320 protected final Match lhs; 321 protected final Match rhs; 322 323 public BinaryMatch(Match lhs, Match rhs) { 324 this.lhs = lhs; 325 this.rhs = rhs; 326 } 327 328 public Match getLhs() { 329 return lhs; 330 } 331 332 public Match getRhs() { 333 return rhs; 334 } 335 } 336 337 /** 338 * Matches every OsmPrimitive. 339 */ 90 340 public static class Always extends Match { 91 341 public static Always INSTANCE = new Always(); 92 342 @Override public boolean match(OsmPrimitive osm) { … … 94 344 } 95 345 } 96 346 347 /** 348 * Never matches any OsmPrimitive. 349 */ 97 350 public static class Never extends Match { 98 351 @Override 99 352 public boolean match(OsmPrimitive osm) { … … 101 354 } 102 355 } 103 356 104 public static class Not extends Match { 105 private final Match match; 106 public Not(Match match) {this.match = match;} 357 /** 358 * Inverts the match. 359 */ 360 public static class Not extends UnaryMatch { 361 public Not(Match match) {super(match);} 107 362 @Override public boolean match(OsmPrimitive osm) { 108 363 return !match.match(osm); 109 364 } … … 113 368 } 114 369 } 115 370 371 /** 372 * Matches if the value of the corresponding key is ''yes'', ''true'', ''1'' or ''on''. 373 */ 116 374 private static class BooleanMatch extends Match { 117 375 private final String key; 118 376 private final boolean defaultValue; … … 131 389 } 132 390 } 133 391 134 public static class And extends Match { 135 private final Match lhs; 136 private final Match rhs; 137 public And(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;} 392 /** 393 * Matches if both left and right expressions match. 394 */ 395 public static class And extends BinaryMatch { 396 public And(Match lhs, Match rhs) {super(lhs, rhs);} 138 397 @Override public boolean match(OsmPrimitive osm) { 139 398 return lhs.match(osm) && rhs.match(osm); 140 399 } 141 @Override public String toString() {return lhs+" && "+rhs;} 142 public Match getLhs() { 143 return lhs; 400 @Override public String toString() { 401 return lhs + " && " + rhs; 144 402 } 145 public Match getRhs() {146 return rhs;147 }148 403 } 149 404 150 public static class Or extends Match { 151 private final Match lhs; 152 private final Match rhs; 153 public Or(Match lhs, Match rhs) {this.lhs = lhs; this.rhs = rhs;} 405 /** 406 * Matches if the left OR the right expression match. 407 */ 408 public static class Or extends BinaryMatch { 409 public Or(Match lhs, Match rhs) {super(lhs, rhs);} 154 410 @Override public boolean match(OsmPrimitive osm) { 155 411 return lhs.match(osm) || rhs.match(osm); 156 412 } 157 @Override public String toString() {return lhs+" || "+rhs;} 158 public Match getLhs() { 159 return lhs; 413 @Override public String toString() { 414 return lhs + " || " + rhs; 160 415 } 161 public Match getRhs() { 162 return rhs; 416 } 417 418 /** 419 * Matches if the left OR the right expression match, but not both. 420 */ 421 public static class Xor extends BinaryMatch { 422 public Xor(Match lhs, Match rhs) {super(lhs, rhs);} 423 @Override public boolean match(OsmPrimitive osm) { 424 return lhs.match(osm) ^ rhs.match(osm); 163 425 } 426 @Override public String toString() { 427 return lhs + " ^ " + rhs; 428 } 164 429 } 165 430 431 /** 432 * Matches objects with the given object ID. 433 */ 166 434 private static class Id extends Match { 167 435 private long id; 168 436 public Id(long id) { 169 437 this.id = id; 170 438 } 439 public Id(PushbackTokenizer tokenizer) throws ParseError { 440 this(tokenizer.readNumber(tr("Primitive id expected"))); 441 } 171 442 @Override public boolean match(OsmPrimitive osm) { 172 443 return id == 0?osm.isNew():osm.getUniqueId() == id; 173 444 } 174 445 @Override public String toString() {return "id="+id;} 175 446 } 176 447 448 /** 449 * Matches objects with the given changeset ID. 450 */ 177 451 private static class ChangesetId extends Match { 178 452 private long changesetid; 179 453 public ChangesetId(long changesetid) {this.changesetid = changesetid;} 454 public ChangesetId(PushbackTokenizer tokenizer) throws ParseError { 455 this(tokenizer.readNumber(tr("Changeset id expected"))); 456 } 180 457 @Override public boolean match(OsmPrimitive osm) { 181 458 return osm.getChangesetId() == changesetid; 182 459 } 183 460 @Override public String toString() {return "changeset="+changesetid;} 184 461 } 185 462 463 /** 464 * Matches objects with the given version number. 465 */ 186 466 private static class Version extends Match { 187 467 private long version; 188 468 public Version(long version) {this.version = version;} 469 public Version(PushbackTokenizer tokenizer) throws ParseError { 470 this(tokenizer.readNumber(tr("Version expected"))); 471 } 189 472 @Override public boolean match(OsmPrimitive osm) { 190 473 return osm.getVersion() == version; 191 474 } 192 475 @Override public String toString() {return "version="+version;} 193 476 } 194 477 478 /** 479 * Matches objects with the given key-value pair. 480 */ 195 481 private static class KeyValue extends Match { 196 482 private final String key; 197 483 private final Pattern keyPattern; … … 286 572 @Override public String toString() {return key+"="+value;} 287 573 } 288 574 575 /** 576 * Matches objects with the exact given key-value pair. 577 */ 289 578 public static class ExactKeyValue extends Match { 290 579 291 580 private enum Mode { … … 414 703 415 704 } 416 705 706 /** 707 * Match a string in any tags (key or value), with optional regex and case insensitivity. 708 */ 417 709 private static class Any extends Match { 418 710 private final String search; 419 711 private final Pattern searchRegex; … … 477 769 } 478 770 } 479 771 772 // TODO: change how we handle this 480 773 private static class ExactType extends Match { 481 774 private final Class<?> type; 482 775 public ExactType(String type) throws ParseError { … … 496 789 @Override public String toString() {return "type="+type;} 497 790 } 498 791 792 /** 793 * Matches objects last changed by the given username. 794 */ 499 795 private static class UserMatch extends Match { 500 796 private String user; 501 797 public UserMatch(String user) { … … 518 814 } 519 815 } 520 816 817 /** 818 * Matches objects with the given relation role (i.e. "outer"). 819 */ 521 820 private static class RoleMatch extends Match { 522 821 private String role; 523 822 public RoleMatch(String role) { … … 548 847 } 549 848 } 550 849 850 /** 851 * Matches objects with properties in a certain range. 852 */ 551 853 private abstract static class CountRange extends Match { 552 854 553 855 private long minCount; … … 558 860 this.maxCount = Math.max(minCount, maxCount); 559 861 } 560 862 863 public CountRange(Range range) { 864 this(range.getStart(), range.getEnd()); 865 } 866 561 867 protected abstract Long getCount(OsmPrimitive osm); 562 868 563 869 protected abstract String getCountString(); … … 578 884 } 579 885 580 886 581 887 /** 888 * Matches ways with a number of nodes in given range 889 */ 582 890 private static class NodeCountRange extends CountRange { 891 public NodeCountRange(Range range) { 892 super(range); 893 } 583 894 584 public NodeCountRange( long minCount, long maxCount){585 super(minCount, maxCount);895 public NodeCountRange(PushbackTokenizer tokenizer) throws ParseError { 896 this(tokenizer.readRange(tr("Range of numbers expected"))); 586 897 } 587 898 588 899 @Override … … 599 910 } 600 911 } 601 912 913 /** 914 * Matches objects with a number of tags in given range 915 */ 602 916 private static class TagCountRange extends CountRange { 917 public TagCountRange(Range range) { 918 super(range); 919 } 603 920 604 public TagCountRange( long minCount, long maxCount){605 super(minCount, maxCount);921 public TagCountRange(PushbackTokenizer tokenizer) throws ParseError { 922 this(tokenizer.readRange(tr("Range of numbers expected"))); 606 923 } 607 924 608 925 @Override … … 616 933 } 617 934 } 618 935 936 /** 937 * Matches objects with a timestamp in given range 938 */ 619 939 private static class TimestampRange extends CountRange { 620 940 621 941 public TimestampRange(long minCount, long maxCount) { … … 634 954 635 955 } 636 956 957 /** 958 * Matches objects that are new (i.e. have not been uploaded to the server) 959 */ 637 960 private static class New extends Match { 638 961 @Override public boolean match(OsmPrimitive osm) { 639 962 return osm.isNew(); … … 643 966 } 644 967 } 645 968 969 /** 970 * Matches all objects that have been modified, created, or undeleted 971 */ 646 972 private static class Modified extends Match { 647 973 @Override public boolean match(OsmPrimitive osm) { 648 974 return osm.isModified() || osm.isNewOrUndeleted(); … … 650 976 @Override public String toString() {return "modified";} 651 977 } 652 978 979 /** 980 * Matches all objects currently selected 981 */ 653 982 private static class Selected extends Match { 654 983 @Override public boolean match(OsmPrimitive osm) { 655 984 return Main.main.getCurrentDataSet().isSelected(osm); … … 657 986 @Override public String toString() {return "selected";} 658 987 } 659 988 989 /** 990 * Match objects that are incomplete, where only id and type are known. 991 * Typically some members of a relation are incomplete until they are 992 * fetched from the server. 993 */ 660 994 private static class Incomplete extends Match { 661 995 @Override public boolean match(OsmPrimitive osm) { 662 996 return osm.isIncomplete(); … … 664 998 @Override public String toString() {return "incomplete";} 665 999 } 666 1000 1001 /** 1002 * Matches objects that don't have any interesting tags (i.e. only has source, 1003 * FIXME, etc.). The complete list of uninteresting tags can be found here: 1004 * org.openstreetmap.josm.data.osm.OsmPrimitive.getUninterestingKeys() 1005 */ 667 1006 private static class Untagged extends Match { 668 1007 @Override public boolean match(OsmPrimitive osm) { 669 1008 return !osm.isTagged() && !osm.isIncomplete(); … … 671 1010 @Override public String toString() {return "untagged";} 672 1011 } 673 1012 1013 /** 1014 * Matches ways which are closed (i.e. first and last node are the same) 1015 */ 674 1016 private static class Closed extends Match { 675 1017 @Override public boolean match(OsmPrimitive osm) { 676 1018 return osm instanceof Way && ((Way) osm).isClosed(); … … 678 1020 @Override public String toString() {return "closed";} 679 1021 } 680 1022 681 public static class Parent extends Match { 682 private final Match child; 1023 /** 1024 * Matches objects if they are parents of the expression 1025 */ 1026 public static class Parent extends UnaryMatch { 683 1027 public Parent(Match m) { 684 if (m == null) { 685 // "parent" (null) should mean the same as "parent()" 686 // (Always). I.e. match everything 687 child = new Always(); 688 } else { 689 child = m; 690 } 1028 super(m); 691 1029 } 692 1030 @Override public boolean match(OsmPrimitive osm) { 693 1031 boolean isParent = false; 694 1032 695 1033 if (osm instanceof Way) { 696 1034 for (Node n : ((Way)osm).getNodes()) { 697 isParent |= child.match(n);1035 isParent |= match.match(n); 698 1036 } 699 1037 } else if (osm instanceof Relation) { 700 1038 for (RelationMember member : ((Relation)osm).getMembers()) { 701 isParent |= child.match(member.getMember());1039 isParent |= match.match(member.getMember()); 702 1040 } 703 1041 } 704 1042 return isParent; 705 1043 } 706 @Override public String toString() {return "parent(" + child + ")";} 707 public Match getChild() { 708 return child; 709 } 1044 @Override public String toString() {return "parent(" + match + ")";} 710 1045 } 711 1046 712 public static class Child extends Match { 713 private final Match parent; 1047 /** 1048 * Matches objects if they are children of the expression 1049 */ 1050 public static class Child extends UnaryMatch { 714 1051 715 1052 public Child(Match m) { 716 // "child" (null) should mean the same as "child()" 717 // (Always). I.e. match everything 718 if (m == null) { 719 parent = new Always(); 720 } else { 721 parent = m; 722 } 1053 super(m); 723 1054 } 724 1055 725 1056 @Override public boolean match(OsmPrimitive osm) { 726 1057 boolean isChild = false; 727 1058 for (OsmPrimitive p : osm.getReferrers()) { 728 isChild |= parent.match(p);1059 isChild |= match.match(p); 729 1060 } 730 1061 return isChild; 731 1062 } 732 @Override public String toString() {return "child(" + parent + ")";} 733 734 public Match getParent() { 735 return parent; 736 } 1063 @Override public String toString() {return "child(" + match + ")";} 737 1064 } 738 1065 739 1066 /** 740 * Matches on the area of a closed way.1067 * Matches if the size of the area is within the given range 741 1068 * 742 1069 * @author Ole Jørgen Brønner 743 1070 */ 744 private static class Area extends CountRange {1071 private static class AreaSize extends CountRange { 745 1072 746 public Area (long minCount, long maxCount) {747 super( minCount, maxCount);1073 public AreaSize(Range range) { 1074 super(range); 748 1075 } 749 1076 1077 public AreaSize(PushbackTokenizer tokenizer) throws ParseError { 1078 this(tokenizer.readRange(tr("Range of numbers expected"))); 1079 } 1080 750 1081 @Override 751 1082 protected Long getCount(OsmPrimitive osm) { 752 1083 if (!(osm instanceof Way && ((Way) osm).isClosed())) … … 757 1088 758 1089 @Override 759 1090 protected String getCountString() { 760 return "area ";1091 return "areasize"; 761 1092 } 762 1093 } 763 1094 764 1095 /** 765 * Matches data within bounds.1096 * Matches objects within the given bounds. 766 1097 */ 767 1098 private abstract static class InArea extends Match { 768 1099 … … 796 1127 } 797 1128 798 1129 /** 799 * Matches datain source area ("downloaded area").1130 * Matches objects within source area ("downloaded area"). 800 1131 */ 801 1132 private static class InDataSourceArea extends InArea { 802 1133 … … 811 1142 } 812 1143 813 1144 /** 814 * Matches datain current map view.1145 * Matches objects within current map view. 815 1146 */ 816 1147 private static class InView extends InArea { 817 1148 … … 842 1173 .parse(); 843 1174 } 844 1175 1176 /** 1177 * Parse search string. 1178 * 1179 * @return match determined by search string 1180 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1181 */ 845 1182 public Match parse() throws ParseError { 846 1183 Match m = parseExpression(); 847 1184 if (!tokenizer.readIfEqual(Token.EOF)) … … 851 1188 return m; 852 1189 } 853 1190 1191 /** 1192 * Parse expression. This is a recursive method. 1193 * 1194 * @return match determined by parsing expression 1195 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1196 */ 854 1197 private Match parseExpression() throws ParseError { 855 1198 Match factor = parseFactor(); 856 1199 if (factor == null) 1200 // empty search string 857 1201 return null; 858 1202 if (tokenizer.readIfEqual(Token.OR)) 859 1203 return new Or(factor, parseExpression(tr("Missing parameter for OR"))); 1204 else if (tokenizer.readIfEqual(Token.XOR)) 1205 return new Xor(factor, parseExpression(tr("Missing parameter for XOR"))); 860 1206 else { 861 1207 Match expression = parseExpression(); 862 1208 if (expression == null) 1209 // reached end of search string, no more recursive calls 863 1210 return factor; 864 1211 else 1212 // the default operator is AND 865 1213 return new And(factor, expression); 866 1214 } 867 1215 } 868 1216 1217 /** 1218 * Parse expression, showing the specified error message if parsing fails. 1219 * 1220 * @param errorMessage to display if parsing error occurs 1221 * @return 1222 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1223 */ 869 1224 private Match parseExpression(String errorMessage) throws ParseError { 870 1225 Match expression = parseExpression(); 871 1226 if (expression == null) … … 874 1229 return expression; 875 1230 } 876 1231 1232 /** 1233 * Parse next factor (a search operator or search term). 1234 * 1235 * @return match determined by parsing factor string 1236 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1237 */ 877 1238 private Match parseFactor() throws ParseError { 878 1239 if (tokenizer.readIfEqual(Token.LEFT_PARENT)) { 879 1240 Match expression = parseExpression(); 880 1241 if (!tokenizer.readIfEqual(Token.RIGHT_PARENT)) 881 1242 throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken()); 882 1243 return expression; 883 } else if (tokenizer.readIfEqual(Token.NOT)) 1244 } else if (tokenizer.readIfEqual(Token.NOT)) { 884 1245 return new Not(parseFactor(tr("Missing operator for NOT"))); 885 else if (tokenizer.readIfEqual(Token.KEY)) { 1246 } else if (tokenizer.readIfEqual(Token.KEY)) { 1247 // factor consists of key:value or key=value 886 1248 String key = tokenizer.getText(); 887 1249 if (tokenizer.readIfEqual(Token.EQUALS)) 888 1250 return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber()); 889 1251 else if (tokenizer.readIfEqual(Token.COLON)) { 890 if ("id".equals(key)) 891 return new Id(tokenizer.readNumber(tr("Primitive id expected"))); 892 else if ("tags".equals(key)) { 893 Range range = tokenizer.readRange(tr("Range of numbers expected")); 894 return new TagCountRange(range.getStart(), range.getEnd()); 895 } else if ("nodes".equals(key)) { 896 Range range = tokenizer.readRange(tr("Range of numbers expected")); 897 return new NodeCountRange(range.getStart(), range.getEnd()); 898 } else if ("areasize".equals(key)) { 899 Range range = tokenizer.readRange(tr("Range of numbers expected")); 900 return new Area(range.getStart(), range.getEnd()); 901 } else if ("timestamp".equals(key)) { 902 String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 903 String[] rangeA = rangeS.split("/"); 904 if (rangeA.length == 1) { 905 return new KeyValue(key, rangeS, regexSearch, caseSensitive); 906 } else if (rangeA.length == 2) { 907 String rangeA1 = rangeA[0].trim(); 908 String rangeA2 = rangeA[1].trim(); 909 long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date 910 long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now" 911 return new TimestampRange(minDate, maxDate); 912 } else { 913 /* I18n: Don't translate timestamp keyword */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i> after ''timestamp''")); 914 } 915 } else if ("changeset".equals(key)) 916 return new ChangesetId(tokenizer.readNumber(tr("Changeset id expected"))); 917 else if ("version".equals(key)) 918 return new Version(tokenizer.readNumber(tr("Version expected"))); 919 else 920 return parseKV(key, tokenizer.readTextOrNumber()); 1252 // see if we have a Match that takes a data parameter 1253 Match match = matchFactory.getSimpleMatch(key, tokenizer); 1254 if (match != null) { 1255 return match; 1256 } 1257 1258 if (matchFactory.isUnaryMatch(key)) 1259 return matchFactory.getUnaryMatch(key, parseFactor(), tokenizer); 1260 1261 // key:value form where value is a string (may be OSM key search) 1262 return parseKV(key, tokenizer.readTextOrNumber()); 921 1263 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK)) 922 1264 return new BooleanMatch(key, false); 923 else if ("new".equals(key)) 924 return new New(); 925 else if ("modified".equals(key)) 926 return new Modified(); 927 else if ("incomplete".equals(key)) 928 return new Incomplete(); 929 else if ("untagged".equals(key)) 930 return new Untagged(); 931 else if ("selected".equals(key)) 932 return new Selected(); 933 else if ("closed".equals(key)) 934 return new Closed(); 935 else if ("child".equals(key)) 936 return new Child(parseFactor()); 937 else if ("parent".equals(key)) 938 return new Parent(parseFactor()); 939 else if ("indownloadedarea".equals(key)) 940 return new InDataSourceArea(false); 941 else if ("allindownloadedarea".equals(key)) 942 return new InDataSourceArea(true); 943 else if ("inview".equals(key)) 944 return new InView(false); 945 else if ("allinview".equals(key)) 946 return new InView(true); 947 else 1265 else { 1266 Match match = matchFactory.getSimpleMatch(key, null); 1267 if (match != null) { 1268 return match; 1269 } 1270 1271 if (matchFactory.isUnaryMatch(key)) 1272 return matchFactory.getUnaryMatch(key, parseFactor(), tokenizer); 1273 1274 // match string in any key or value 948 1275 return new Any(key, regexSearch, caseSensitive); 1276 } 949 1277 } else 950 1278 return null; 951 1279 } -
src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java
183 183 184 184 private Match transform(Match m, int searchExpressionPosition) throws ParseError { 185 185 if (m instanceof Parent) { 186 Match child = transform(((Parent) m).get Child(), searchExpressionPosition);186 Match child = transform(((Parent) m).getOperand(), searchExpressionPosition); 187 187 return new ParentSet(child); 188 188 } else if (m instanceof Child) { 189 Match parent = transform(((Child) m).get Parent(), searchExpressionPosition);189 Match parent = transform(((Child) m).getOperand(), searchExpressionPosition); 190 190 return new ChildSet(parent); 191 191 } else if (m instanceof And) { 192 192 Match lhs = transform(((And) m).getLhs(), searchExpressionPosition);