Ticket #7178: factory_plugin_search_operators2.patch
File factory_plugin_search_operators2.patch, 34.6 KB (added by , 14 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
7 7 import java.io.PushbackReader; 8 8 import java.io.StringReader; 9 9 import java.text.Normalizer; 10 import java.util.Collection; 11 import java.util.Date; 10 import java.util.*; 12 11 import java.util.regex.Matcher; 13 12 import java.util.regex.Pattern; 14 13 import java.util.regex.PatternSyntaxException; … … 54 53 private static String rxErrorMsg = marktr("The regex \"{0}\" had a parse error at offset {1}, full error:\n\n{2}"); 55 54 private static String rxErrorMsgNoPos = marktr("The regex \"{0}\" had a parse error, full error:\n\n{1}"); 56 55 private PushbackTokenizer tokenizer; 56 private static Map<String, SimpleMatchFactory> simpleMatchFactoryMap = new HashMap<String, SimpleMatchFactory>(); 57 private static Map<String, UnaryMatchFactory> unaryMatchFactoryMap = new HashMap<String, UnaryMatchFactory>(); 58 private static Map<String, BinaryMatchFactory> binaryMatchFactoryMap = new HashMap<String, BinaryMatchFactory>(); 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 factories at first instance, so plugins should 66 * never be able to generate a NPE 67 */ 68 if (simpleMatchFactoryMap.isEmpty()) 69 addMatchFactory(new CoreSimpleMatchFactory()); 70 if (unaryMatchFactoryMap.isEmpty()) 71 addMatchFactory(new CoreUnaryMatchFactory()); 72 62 73 } 74 75 /** 76 * Add (register) MatchFactory with SearchCompiler 77 * @param factory 78 */ 79 public static void addMatchFactory(MatchFactory factory) { 80 for (String keyword : factory.getKeywords()) { 81 // TODO: check for keyword collisions 82 if (factory instanceof SimpleMatchFactory) 83 simpleMatchFactoryMap.put(keyword, (SimpleMatchFactory)factory); 84 else if (factory instanceof UnaryMatchFactory) 85 unaryMatchFactoryMap.put(keyword, (UnaryMatchFactory)factory); 86 else if (factory instanceof BinaryMatchFactory) 87 binaryMatchFactoryMap.put(keyword, (BinaryMatchFactory)factory); 88 else 89 throw new AssertionError("Unknown match factory"); 90 } 91 } 63 92 93 public class CoreSimpleMatchFactory implements SimpleMatchFactory { 94 private Collection<String> keywords = Arrays.asList("id", "version", 95 "changeset", "nodes", "tags", "areasize", "modified", "selected", 96 "incomplete", "untagged", "closed", "new", "indownloadarea", 97 "allindownloadarea", "inview", "allinview", "timestamp"); 98 99 @Override 100 public Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError { 101 if ("id".equals(keyword)) { 102 return new Id(tokenizer); 103 } else if ("version".equals(keyword)) { 104 return new Version(tokenizer); 105 } else if ("changeset".equals(keyword)) { 106 return new ChangesetId(tokenizer); 107 } else if ("nodes".equals(keyword)) { 108 return new NodeCountRange(tokenizer); 109 } else if ("tags".equals(keyword)) { 110 return new TagCountRange(tokenizer); 111 } else if ("areasize".equals(keyword)) { 112 return new AreaSize(tokenizer); 113 } else if ("modified".equals(keyword)) { 114 return new Modified(); 115 } else if ("selected".equals(keyword)) { 116 return new Selected(); 117 } else if ("incomplete".equals(keyword)) { 118 return new Incomplete(); 119 } else if ("untagged".equals(keyword)) { 120 return new Untagged(); 121 } else if ("closed".equals(keyword)) { 122 return new Closed(); 123 } else if ("new".equals(keyword)) { 124 return new New(); 125 } else if ("indownloadedarea".equals(keyword)) { 126 return new InDataSourceArea(false); 127 } else if ("allindownloadedarea".equals(keyword)) { 128 return new InDataSourceArea(true); 129 } else if ("inview".equals(keyword)) { 130 return new InView(false); 131 } else if ("allinview".equals(keyword)) { 132 return new InView(true); 133 } else if ("timestamp".equals(keyword)) { 134 String rangeS = " " + tokenizer.readTextOrNumber() + " "; // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 135 String[] rangeA = rangeS.split("/"); 136 if (rangeA.length == 1) { 137 return new KeyValue(keyword, rangeS, regexSearch, caseSensitive); 138 } else if (rangeA.length == 2) { 139 String rangeA1 = rangeA[0].trim(); 140 String rangeA2 = rangeA[1].trim(); 141 long minDate = DateUtils.fromString(rangeA1.isEmpty() ? "1980" : rangeA1).getTime(); // if min timestap is empty: use lowest possible date 142 long maxDate = rangeA2.isEmpty() ? new Date().getTime() : DateUtils.fromString(rangeA2).getTime(); // if max timestamp is empty: use "now" 143 return new TimestampRange(minDate, maxDate); 144 } else { 145 /* 146 * I18n: Don't translate timestamp keyword 147 */ throw new ParseError(tr("Expecting <i>min</i>/<i>max</i> after ''timestamp''")); 148 } 149 } else { 150 return null; 151 } 152 } 153 154 @Override 155 public Collection<String> getKeywords() { 156 return keywords; 157 } 158 } 159 160 public static class CoreUnaryMatchFactory implements UnaryMatchFactory { 161 private static Collection<String> keywords = Arrays.asList("parent", "child"); 162 163 @Override 164 public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) { 165 if ("parent".equals(keyword)) { 166 return new Parent(matchOperand); 167 } else if ("child".equals(keyword)) { 168 return new Child(matchOperand); 169 } 170 return null; 171 } 172 173 @Override 174 public Collection<String> getKeywords() { 175 return keywords; 176 } 177 } 178 179 /** 180 * Classes implementing this interface can provide Match operators. 181 */ 182 private interface MatchFactory { 183 public Collection<String> getKeywords(); 184 } 185 186 public interface SimpleMatchFactory extends MatchFactory { 187 public Match get(String keyword, PushbackTokenizer tokenizer) throws ParseError; 188 } 189 190 public interface UnaryMatchFactory extends MatchFactory { 191 public UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws ParseError; 192 } 193 194 public interface BinaryMatchFactory extends MatchFactory { 195 public BinaryMatch get(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError; 196 } 197 198 /** 199 * Base class for all search operators. 200 */ 64 201 abstract public static class Match { 202 65 203 abstract public boolean match(OsmPrimitive osm); 66 204 67 205 /** … … 69 207 */ 70 208 protected boolean existsMatch(Collection<? extends OsmPrimitive> primitives) { 71 209 for (OsmPrimitive p : primitives) { 72 if (match(p)) 210 if (match(p)) { 73 211 return true; 212 } 74 213 } 75 214 return false; 76 215 } … … 80 219 */ 81 220 protected boolean forallMatch(Collection<? extends OsmPrimitive> primitives) { 82 221 for (OsmPrimitive p : primitives) { 83 if (!match(p)) 222 if (!match(p)) { 84 223 return false; 224 } 85 225 } 86 226 return true; 87 227 } 88 228 } 89 229 230 /** 231 * A unary search operator which may take data parameters. 232 */ 233 abstract public static class UnaryMatch extends Match { 234 235 protected final Match match; 236 237 public UnaryMatch(Match match) { 238 if (match == null) { 239 // "operator" (null) should mean the same as "operator()" 240 // (Always). I.e. match everything 241 this.match = new Always(); 242 } else { 243 this.match = match; 244 } 245 } 246 247 public Match getOperand() { 248 return match; 249 } 250 } 251 252 /** 253 * A binary search operator which may take data parameters. 254 */ 255 abstract public static class BinaryMatch extends Match { 256 257 protected final Match lhs; 258 protected final Match rhs; 259 260 public BinaryMatch(Match lhs, Match rhs) { 261 this.lhs = lhs; 262 this.rhs = rhs; 263 } 264 265 public Match getLhs() { 266 return lhs; 267 } 268 269 public Match getRhs() { 270 return rhs; 271 } 272 } 273 274 /** 275 * Matches every OsmPrimitive. 276 */ 90 277 public static class Always extends Match { 91 278 public static Always INSTANCE = new Always(); 92 279 @Override public boolean match(OsmPrimitive osm) { … … 94 281 } 95 282 } 96 283 284 /** 285 * Never matches any OsmPrimitive. 286 */ 97 287 public static class Never extends Match { 98 288 @Override 99 289 public boolean match(OsmPrimitive osm) { … … 101 291 } 102 292 } 103 293 104 public static class Not extends Match { 105 private final Match match; 106 public Not(Match match) {this.match = match;} 294 /** 295 * Inverts the match. 296 */ 297 public static class Not extends UnaryMatch { 298 public Not(Match match) {super(match);} 107 299 @Override public boolean match(OsmPrimitive osm) { 108 300 return !match.match(osm); 109 301 } … … 113 305 } 114 306 } 115 307 308 /** 309 * Matches if the value of the corresponding key is ''yes'', ''true'', ''1'' or ''on''. 310 */ 116 311 private static class BooleanMatch extends Match { 117 312 private final String key; 118 313 private final boolean defaultValue; … … 131 326 } 132 327 } 133 328 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;} 329 /** 330 * Matches if both left and right expressions match. 331 */ 332 public static class And extends BinaryMatch { 333 public And(Match lhs, Match rhs) {super(lhs, rhs);} 138 334 @Override public boolean match(OsmPrimitive osm) { 139 335 return lhs.match(osm) && rhs.match(osm); 140 336 } 141 @Override public String toString() {return lhs+" && "+rhs;} 142 public Match getLhs() { 143 return lhs; 337 @Override public String toString() { 338 return lhs + " && " + rhs; 144 339 } 145 public Match getRhs() {146 return rhs;147 }148 340 } 149 341 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;} 342 /** 343 * Matches if the left OR the right expression match. 344 */ 345 public static class Or extends BinaryMatch { 346 public Or(Match lhs, Match rhs) {super(lhs, rhs);} 154 347 @Override public boolean match(OsmPrimitive osm) { 155 348 return lhs.match(osm) || rhs.match(osm); 156 349 } 157 @Override public String toString() {return lhs+" || "+rhs;} 158 public Match getLhs() { 159 return lhs; 350 @Override public String toString() { 351 return lhs + " || " + rhs; 160 352 } 161 public Match getRhs() { 162 return rhs; 353 } 354 355 /** 356 * Matches if the left OR the right expression match, but not both. 357 */ 358 public static class Xor extends BinaryMatch { 359 public Xor(Match lhs, Match rhs) {super(lhs, rhs);} 360 @Override public boolean match(OsmPrimitive osm) { 361 return lhs.match(osm) ^ rhs.match(osm); 163 362 } 363 @Override public String toString() { 364 return lhs + " ^ " + rhs; 365 } 164 366 } 165 367 368 /** 369 * Matches objects with the given object ID. 370 */ 166 371 private static class Id extends Match { 167 372 private long id; 168 373 public Id(long id) { 169 374 this.id = id; 170 375 } 376 public Id(PushbackTokenizer tokenizer) throws ParseError { 377 this(tokenizer.readNumber(tr("Primitive id expected"))); 378 } 171 379 @Override public boolean match(OsmPrimitive osm) { 172 380 return id == 0?osm.isNew():osm.getUniqueId() == id; 173 381 } 174 382 @Override public String toString() {return "id="+id;} 175 383 } 176 384 385 /** 386 * Matches objects with the given changeset ID. 387 */ 177 388 private static class ChangesetId extends Match { 178 389 private long changesetid; 179 390 public ChangesetId(long changesetid) {this.changesetid = changesetid;} 391 public ChangesetId(PushbackTokenizer tokenizer) throws ParseError { 392 this(tokenizer.readNumber(tr("Changeset id expected"))); 393 } 180 394 @Override public boolean match(OsmPrimitive osm) { 181 395 return osm.getChangesetId() == changesetid; 182 396 } 183 397 @Override public String toString() {return "changeset="+changesetid;} 184 398 } 185 399 400 /** 401 * Matches objects with the given version number. 402 */ 186 403 private static class Version extends Match { 187 404 private long version; 188 405 public Version(long version) {this.version = version;} 406 public Version(PushbackTokenizer tokenizer) throws ParseError { 407 this(tokenizer.readNumber(tr("Version expected"))); 408 } 189 409 @Override public boolean match(OsmPrimitive osm) { 190 410 return osm.getVersion() == version; 191 411 } 192 412 @Override public String toString() {return "version="+version;} 193 413 } 194 414 415 /** 416 * Matches objects with the given key-value pair. 417 */ 195 418 private static class KeyValue extends Match { 196 419 private final String key; 197 420 private final Pattern keyPattern; … … 286 509 @Override public String toString() {return key+"="+value;} 287 510 } 288 511 512 /** 513 * Matches objects with the exact given key-value pair. 514 */ 289 515 public static class ExactKeyValue extends Match { 290 516 291 517 private enum Mode { … … 414 640 415 641 } 416 642 643 /** 644 * Match a string in any tags (key or value), with optional regex and case insensitivity. 645 */ 417 646 private static class Any extends Match { 418 647 private final String search; 419 648 private final Pattern searchRegex; … … 477 706 } 478 707 } 479 708 709 // TODO: change how we handle this 480 710 private static class ExactType extends Match { 481 711 private final Class<?> type; 482 712 public ExactType(String type) throws ParseError { … … 496 726 @Override public String toString() {return "type="+type;} 497 727 } 498 728 729 /** 730 * Matches objects last changed by the given username. 731 */ 499 732 private static class UserMatch extends Match { 500 733 private String user; 501 734 public UserMatch(String user) { … … 518 751 } 519 752 } 520 753 754 /** 755 * Matches objects with the given relation role (i.e. "outer"). 756 */ 521 757 private static class RoleMatch extends Match { 522 758 private String role; 523 759 public RoleMatch(String role) { … … 548 784 } 549 785 } 550 786 787 /** 788 * Matches objects with properties in a certain range. 789 */ 551 790 private abstract static class CountRange extends Match { 552 791 553 792 private long minCount; … … 558 797 this.maxCount = Math.max(minCount, maxCount); 559 798 } 560 799 800 public CountRange(Range range) { 801 this(range.getStart(), range.getEnd()); 802 } 803 561 804 protected abstract Long getCount(OsmPrimitive osm); 562 805 563 806 protected abstract String getCountString(); … … 578 821 } 579 822 580 823 581 824 /** 825 * Matches ways with a number of nodes in given range 826 */ 582 827 private static class NodeCountRange extends CountRange { 828 public NodeCountRange(Range range) { 829 super(range); 830 } 583 831 584 public NodeCountRange( long minCount, long maxCount){585 super(minCount, maxCount);832 public NodeCountRange(PushbackTokenizer tokenizer) throws ParseError { 833 this(tokenizer.readRange(tr("Range of numbers expected"))); 586 834 } 587 835 588 836 @Override … … 599 847 } 600 848 } 601 849 850 /** 851 * Matches objects with a number of tags in given range 852 */ 602 853 private static class TagCountRange extends CountRange { 854 public TagCountRange(Range range) { 855 super(range); 856 } 603 857 604 public TagCountRange( long minCount, long maxCount){605 super(minCount, maxCount);858 public TagCountRange(PushbackTokenizer tokenizer) throws ParseError { 859 this(tokenizer.readRange(tr("Range of numbers expected"))); 606 860 } 607 861 608 862 @Override … … 616 870 } 617 871 } 618 872 873 /** 874 * Matches objects with a timestamp in given range 875 */ 619 876 private static class TimestampRange extends CountRange { 620 877 621 878 public TimestampRange(long minCount, long maxCount) { … … 634 891 635 892 } 636 893 894 /** 895 * Matches objects that are new (i.e. have not been uploaded to the server) 896 */ 637 897 private static class New extends Match { 638 898 @Override public boolean match(OsmPrimitive osm) { 639 899 return osm.isNew(); … … 643 903 } 644 904 } 645 905 906 /** 907 * Matches all objects that have been modified, created, or undeleted 908 */ 646 909 private static class Modified extends Match { 647 910 @Override public boolean match(OsmPrimitive osm) { 648 911 return osm.isModified() || osm.isNewOrUndeleted(); … … 650 913 @Override public String toString() {return "modified";} 651 914 } 652 915 916 /** 917 * Matches all objects currently selected 918 */ 653 919 private static class Selected extends Match { 654 920 @Override public boolean match(OsmPrimitive osm) { 655 921 return Main.main.getCurrentDataSet().isSelected(osm); … … 657 923 @Override public String toString() {return "selected";} 658 924 } 659 925 926 /** 927 * Match objects that are incomplete, where only id and type are known. 928 * Typically some members of a relation are incomplete until they are 929 * fetched from the server. 930 */ 660 931 private static class Incomplete extends Match { 661 932 @Override public boolean match(OsmPrimitive osm) { 662 933 return osm.isIncomplete(); … … 664 935 @Override public String toString() {return "incomplete";} 665 936 } 666 937 938 /** 939 * Matches objects that don't have any interesting tags (i.e. only has source, 940 * FIXME, etc.). The complete list of uninteresting tags can be found here: 941 * org.openstreetmap.josm.data.osm.OsmPrimitive.getUninterestingKeys() 942 */ 667 943 private static class Untagged extends Match { 668 944 @Override public boolean match(OsmPrimitive osm) { 669 945 return !osm.isTagged() && !osm.isIncomplete(); … … 671 947 @Override public String toString() {return "untagged";} 672 948 } 673 949 950 /** 951 * Matches ways which are closed (i.e. first and last node are the same) 952 */ 674 953 private static class Closed extends Match { 675 954 @Override public boolean match(OsmPrimitive osm) { 676 955 return osm instanceof Way && ((Way) osm).isClosed(); … … 678 957 @Override public String toString() {return "closed";} 679 958 } 680 959 681 public static class Parent extends Match { 682 private final Match child; 960 /** 961 * Matches objects if they are parents of the expression 962 */ 963 public static class Parent extends UnaryMatch { 683 964 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 } 965 super(m); 691 966 } 692 967 @Override public boolean match(OsmPrimitive osm) { 693 968 boolean isParent = false; 694 969 695 970 if (osm instanceof Way) { 696 971 for (Node n : ((Way)osm).getNodes()) { 697 isParent |= child.match(n);972 isParent |= match.match(n); 698 973 } 699 974 } else if (osm instanceof Relation) { 700 975 for (RelationMember member : ((Relation)osm).getMembers()) { 701 isParent |= child.match(member.getMember());976 isParent |= match.match(member.getMember()); 702 977 } 703 978 } 704 979 return isParent; 705 980 } 706 @Override public String toString() {return "parent(" + child + ")";} 707 public Match getChild() { 708 return child; 709 } 981 @Override public String toString() {return "parent(" + match + ")";} 710 982 } 711 983 712 public static class Child extends Match { 713 private final Match parent; 984 /** 985 * Matches objects if they are children of the expression 986 */ 987 public static class Child extends UnaryMatch { 714 988 715 989 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 } 990 super(m); 723 991 } 724 992 725 993 @Override public boolean match(OsmPrimitive osm) { 726 994 boolean isChild = false; 727 995 for (OsmPrimitive p : osm.getReferrers()) { 728 isChild |= parent.match(p);996 isChild |= match.match(p); 729 997 } 730 998 return isChild; 731 999 } 732 @Override public String toString() {return "child(" + parent + ")";} 733 734 public Match getParent() { 735 return parent; 736 } 1000 @Override public String toString() {return "child(" + match + ")";} 737 1001 } 738 1002 739 1003 /** 740 * Matches on the area of a closed way.1004 * Matches if the size of the area is within the given range 741 1005 * 742 1006 * @author Ole Jørgen Brønner 743 1007 */ 744 private static class Area extends CountRange {1008 private static class AreaSize extends CountRange { 745 1009 746 public Area (long minCount, long maxCount) {747 super( minCount, maxCount);1010 public AreaSize(Range range) { 1011 super(range); 748 1012 } 749 1013 1014 public AreaSize(PushbackTokenizer tokenizer) throws ParseError { 1015 this(tokenizer.readRange(tr("Range of numbers expected"))); 1016 } 1017 750 1018 @Override 751 1019 protected Long getCount(OsmPrimitive osm) { 752 1020 if (!(osm instanceof Way && ((Way) osm).isClosed())) … … 757 1025 758 1026 @Override 759 1027 protected String getCountString() { 760 return "area ";1028 return "areasize"; 761 1029 } 762 1030 } 763 1031 764 1032 /** 765 * Matches data within bounds.1033 * Matches objects within the given bounds. 766 1034 */ 767 1035 private abstract static class InArea extends Match { 768 1036 … … 796 1064 } 797 1065 798 1066 /** 799 * Matches datain source area ("downloaded area").1067 * Matches objects within source area ("downloaded area"). 800 1068 */ 801 1069 private static class InDataSourceArea extends InArea { 802 1070 … … 811 1079 } 812 1080 813 1081 /** 814 * Matches datain current map view.1082 * Matches objects within current map view. 815 1083 */ 816 1084 private static class InView extends InArea { 817 1085 … … 842 1110 .parse(); 843 1111 } 844 1112 1113 /** 1114 * Parse search string. 1115 * 1116 * @return match determined by search string 1117 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1118 */ 845 1119 public Match parse() throws ParseError { 846 1120 Match m = parseExpression(); 847 1121 if (!tokenizer.readIfEqual(Token.EOF)) … … 851 1125 return m; 852 1126 } 853 1127 1128 /** 1129 * Parse expression. This is a recursive method. 1130 * 1131 * @return match determined by parsing expression 1132 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1133 */ 854 1134 private Match parseExpression() throws ParseError { 855 1135 Match factor = parseFactor(); 856 1136 if (factor == null) 1137 // empty search string 857 1138 return null; 858 1139 if (tokenizer.readIfEqual(Token.OR)) 859 1140 return new Or(factor, parseExpression(tr("Missing parameter for OR"))); 1141 else if (tokenizer.readIfEqual(Token.XOR)) 1142 return new Xor(factor, parseExpression(tr("Missing parameter for XOR"))); 860 1143 else { 861 1144 Match expression = parseExpression(); 862 1145 if (expression == null) 1146 // reached end of search string, no more recursive calls 863 1147 return factor; 864 1148 else 1149 // the default operator is AND 865 1150 return new And(factor, expression); 866 1151 } 867 1152 } 868 1153 1154 /** 1155 * Parse expression, showing the specified error message if parsing fails. 1156 * 1157 * @param errorMessage to display if parsing error occurs 1158 * @return 1159 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1160 */ 869 1161 private Match parseExpression(String errorMessage) throws ParseError { 870 1162 Match expression = parseExpression(); 871 1163 if (expression == null) … … 874 1166 return expression; 875 1167 } 876 1168 1169 /** 1170 * Parse next factor (a search operator or search term). 1171 * 1172 * @return match determined by parsing factor string 1173 * @throws org.openstreetmap.josm.actions.search.SearchCompiler.ParseError 1174 */ 877 1175 private Match parseFactor() throws ParseError { 878 1176 if (tokenizer.readIfEqual(Token.LEFT_PARENT)) { 879 1177 Match expression = parseExpression(); 880 1178 if (!tokenizer.readIfEqual(Token.RIGHT_PARENT)) 881 1179 throw new ParseError(Token.RIGHT_PARENT, tokenizer.nextToken()); 882 1180 return expression; 883 } else if (tokenizer.readIfEqual(Token.NOT)) 1181 } else if (tokenizer.readIfEqual(Token.NOT)) { 884 1182 return new Not(parseFactor(tr("Missing operator for NOT"))); 885 else if (tokenizer.readIfEqual(Token.KEY)) { 1183 } else if (tokenizer.readIfEqual(Token.KEY)) { 1184 // factor consists of key:value or key=value 886 1185 String key = tokenizer.getText(); 887 1186 if (tokenizer.readIfEqual(Token.EQUALS)) 888 1187 return new ExactKeyValue(regexSearch, key, tokenizer.readTextOrNumber()); 889 1188 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()); 1189 // see if we have a Match that takes a data parameter 1190 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key); 1191 if (factory != null) 1192 return factory.get(key, tokenizer); 1193 1194 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key); 1195 if (unaryFactory != null) 1196 return unaryFactory.get(key, parseFactor(), tokenizer); 1197 1198 // key:value form where value is a string (may be OSM key search) 1199 return parseKV(key, tokenizer.readTextOrNumber()); 921 1200 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK)) 922 1201 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 1202 else { 1203 SimpleMatchFactory factory = simpleMatchFactoryMap.get(key); 1204 if (factory != null) 1205 return factory.get(key, null); 1206 1207 UnaryMatchFactory unaryFactory = unaryMatchFactoryMap.get(key); 1208 if (unaryFactory != null) 1209 return unaryFactory.get(key, parseFactor(), null); 1210 1211 // match string in any key or value 948 1212 return new Any(key, regexSearch, caseSensitive); 1213 } 949 1214 } else 950 1215 return null; 951 1216 } -
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);