Changeset 18847 in josm for trunk/src/org/openstreetmap
- Timestamp:
- 2023-10-03T16:25:42+02:00 (14 months ago)
- Location:
- trunk/src/org/openstreetmap/josm
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
r18801 r18847 27 27 import java.util.regex.PatternSyntaxException; 28 28 import java.util.stream.Collectors; 29 30 import javax.annotation.Nonnull; 31 import javax.annotation.Nullable; 29 32 30 33 import org.openstreetmap.josm.data.Bounds; … … 127 130 } 128 131 132 /** 133 * The core factory for "simple" {@link Match} objects 134 */ 129 135 public static class CoreSimpleMatchFactory implements SimpleMatchFactory { 130 136 private final Collection<String> keywords = Arrays.asList("id", "version", "type", "user", "role", … … 156 162 default: 157 163 if (tokenizer != null) { 158 switch (keyword) { 159 case "id": 160 return new Id(tokenizer); 161 case "version": 162 return new Version(tokenizer); 163 case "type": 164 return new ExactType(tokenizer.readTextOrNumber()); 165 case "preset": 166 return new Preset(tokenizer.readTextOrNumber()); 167 case "user": 168 return new UserMatch(tokenizer.readTextOrNumber()); 169 case "role": 170 return new RoleMatch(tokenizer.readTextOrNumber()); 171 case "changeset": 172 return new ChangesetId(tokenizer); 173 case "nodes": 174 return new NodeCountRange(tokenizer); 175 case "ways": 176 return new WayCountRange(tokenizer); 177 case "members": 178 return new MemberCountRange(tokenizer); 179 case "tags": 180 return new TagCountRange(tokenizer); 181 case "areasize": 182 return new AreaSize(tokenizer); 183 case "waylength": 184 return new WayLength(tokenizer); 185 case "nth": 186 return new Nth(tokenizer, false); 187 case "nth%": 188 return new Nth(tokenizer, true); 189 case "hasRole": 190 return new HasRole(tokenizer); 191 case "timestamp": 192 // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 193 String rangeS = ' ' + tokenizer.readTextOrNumber() + ' '; 194 String[] rangeA = rangeS.split("/", -1); 195 if (rangeA.length == 1) { 196 return new KeyValue(keyword, rangeS.trim(), regexSearch, caseSensitive); 197 } else if (rangeA.length == 2) { 198 return TimestampRange.create(rangeA); 199 } else { 200 throw new SearchParseError("<html>" + tr("Expecting {0} after {1}", "<i>min</i>/<i>max</i>", "<i>timestamp</i>") 201 + "</html>"); 202 } 203 } 164 return getTokenizer(keyword, caseSensitive, regexSearch, tokenizer); 204 165 } else { 205 166 throw new SearchParseError("<html>" + tr("Expecting {0} after {1}", "<code>:</code>", "<i>" + keyword + "</i>") + "</html>"); 206 167 } 207 168 } 208 throw new IllegalStateException("Not expecting keyword " + keyword); 169 } 170 171 private static Match getTokenizer(String keyword, boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) 172 throws SearchParseError { 173 switch (keyword) { 174 case "id": 175 return new Id(tokenizer); 176 case "version": 177 return new Version(tokenizer); 178 case "type": 179 return new ExactType(tokenizer.readTextOrNumber()); 180 case "preset": 181 return new Preset(tokenizer.readTextOrNumber()); 182 case "user": 183 return new UserMatch(tokenizer.readTextOrNumber()); 184 case "role": 185 return new RoleMatch(tokenizer.readTextOrNumber()); 186 case "changeset": 187 return new ChangesetId(tokenizer); 188 case "nodes": 189 return new NodeCountRange(tokenizer); 190 case "ways": 191 return new WayCountRange(tokenizer); 192 case "members": 193 return new MemberCountRange(tokenizer); 194 case "tags": 195 return new TagCountRange(tokenizer); 196 case "areasize": 197 return new AreaSize(tokenizer); 198 case "waylength": 199 return new WayLength(tokenizer); 200 case "nth": 201 return new Nth(tokenizer, false); 202 case "nth%": 203 return new Nth(tokenizer, true); 204 case "hasRole": 205 return new HasRole(tokenizer); 206 case "timestamp": 207 // add leading/trailing space in order to get expected split (e.g. "a--" => {"a", ""}) 208 String rangeS = ' ' + tokenizer.readTextOrNumber() + ' '; 209 String[] rangeA = rangeS.split("/", -1); 210 if (rangeA.length == 1) { 211 return new KeyValue(keyword, rangeS.trim(), regexSearch, caseSensitive); 212 } else if (rangeA.length == 2) { 213 return TimestampRange.create(rangeA); 214 } else { 215 throw new SearchParseError("<html>" + tr("Expecting {0} after {1}", "<i>min</i>/<i>max</i>", "<i>timestamp</i>") 216 + "</html>"); 217 } 218 default: 219 throw new IllegalStateException("Not expecting keyword " + keyword); 220 } 209 221 } 210 222 … … 215 227 } 216 228 229 /** 230 * The core {@link UnaryMatch} factory 231 */ 217 232 public static class CoreUnaryMatchFactory implements UnaryMatchFactory { 218 233 private static final Collection<String> keywords = Arrays.asList("parent", "child"); … … 242 257 } 243 258 259 /** 260 * A factory for getting {@link Match} objects 261 */ 244 262 public interface SimpleMatchFactory extends MatchFactory { 263 /** 264 * Get the {@link Match} object 265 * @param keyword The keyword to get/create the correct {@link Match} object 266 * @param caseSensitive {@code true} if the search is case-sensitive 267 * @param regexSearch {@code true} if the search is regex-based 268 * @param tokenizer May be used to construct the {@link Match} object 269 * @return The {@link Match} object for the keyword and its arguments 270 * @throws SearchParseError If the {@link Match} object could not be constructed. 271 */ 245 272 Match get(String keyword, boolean caseSensitive, boolean regexSearch, PushbackTokenizer tokenizer) throws SearchParseError; 246 273 } 247 274 275 /** 276 * A factory for getting {@link UnaryMatch} objects 277 */ 248 278 public interface UnaryMatchFactory extends MatchFactory { 279 /** 280 * Get the {@link UnaryMatch} object 281 * @param keyword The keyword to get/create the correct {@link UnaryMatch} object 282 * @param matchOperand May be used to construct the {@link UnaryMatch} object 283 * @param tokenizer May be used to construct the {@link UnaryMatch} object 284 * @return The {@link UnaryMatch} object for the keyword and its arguments 285 * @throws SearchParseError If the {@link UnaryMatch} object could not be constructed. 286 */ 249 287 UnaryMatch get(String keyword, Match matchOperand, PushbackTokenizer tokenizer) throws SearchParseError; 250 288 } 251 289 290 /** 291 * A factor for getting {@link AbstractBinaryMatch} objects 292 */ 252 293 public interface BinaryMatchFactory extends MatchFactory { 294 /** 295 * Get the {@link AbstractBinaryMatch} object 296 * @param keyword The keyword to get/create the correct {@link AbstractBinaryMatch} object 297 * @param lhs May be used to construct the {@link AbstractBinaryMatch} object (see {@link AbstractBinaryMatch#getLhs()}) 298 * @param rhs May be used to construct the {@link AbstractBinaryMatch} object (see {@link AbstractBinaryMatch#getRhs()}) 299 * @param tokenizer May be used to construct the {@link AbstractBinaryMatch} object 300 * @return The {@link AbstractBinaryMatch} object for the keyword and its arguments 301 * @throws SearchParseError If the {@link AbstractBinaryMatch} object could not be constructed. 302 */ 253 303 AbstractBinaryMatch get(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws SearchParseError; 254 304 } … … 303 353 } 304 354 355 /** 356 * A common subclass of {@link Match} for matching against tags 357 */ 305 358 public abstract static class TaggedMatch extends Match { 306 359 … … 330 383 */ 331 384 public abstract static class UnaryMatch extends Match { 332 385 @Nonnull 333 386 protected final Match match; 334 387 335 protected UnaryMatch( Match match) {388 protected UnaryMatch(@Nullable Match match) { 336 389 if (match == null) { 337 390 // "operator" (null) should mean the same as "operator()" … … 349 402 @Override 350 403 public int hashCode() { 351 return 31 + ((match == null) ? 0 : match.hashCode());404 return 31 + match.hashCode(); 352 405 } 353 406 … … 359 412 return false; 360 413 UnaryMatch other = (UnaryMatch) obj; 361 if (match == null) { 362 if (other.match != null) 363 return false; 364 } else if (!match.equals(other.match)) 365 return false; 366 return true; 414 return match.equals(other.match); 367 415 } 368 416 } … … 430 478 return false; 431 479 AbstractBinaryMatch other = (AbstractBinaryMatch) obj; 432 if (lhs == null) { 433 if (other.lhs != null) 434 return false; 435 } else if (!lhs.equals(other.lhs)) 436 return false; 437 if (rhs == null) { 438 if (other.rhs != null) 439 return false; 440 } else if (!rhs.equals(other.rhs)) 441 return false; 442 return true; 480 return Objects.equals(lhs, other.lhs) && Objects.equals(rhs, other.rhs); 443 481 } 444 482 } … … 536 574 if (defaultValue != other.defaultValue) 537 575 return false; 538 if (key == null) { 539 if (other.key != null) 540 return false; 541 } else if (!key.equals(other.key)) 542 return false; 543 return true; 576 return Objects.equals(key, other.key); 544 577 } 545 578 } … … 794 827 } 795 828 829 /** 830 * Match a primitive based off of a value comparison. This currently supports: 831 * <ul> 832 * <li>ISO8601 dates (YYYY-MM-DD)</li> 833 * <li>Numbers</li> 834 * <li>Alpha-numeric comparison</li> 835 * </ul> 836 */ 796 837 public static class ValueComparison extends TaggedMatch { 797 838 private final String key; … … 801 842 private static final Pattern ISO8601 = Pattern.compile("\\d+-\\d+-\\d+"); 802 843 844 /** 845 * Create a new {@link ValueComparison} object 846 * @param key The key to get the value from 847 * @param referenceValue The value to compare to 848 * @param compareMode The compare mode to use; {@code < 0} is {@code currentValue < referenceValue} and 849 * {@code > 0} is {@code currentValue > referenceValue}. {@code 0} is effectively an equality check. 850 */ 803 851 public ValueComparison(String key, String referenceValue, int compareMode) { 804 852 this.key = key; … … 809 857 v = Double.valueOf(referenceValue); 810 858 } 811 } catch (NumberFormatException ignore) {812 Logging.trace( ignore);859 } catch (NumberFormatException numberFormatException) { 860 Logging.trace(numberFormatException); 813 861 } 814 862 this.referenceNumber = v; … … 855 903 if (compareMode != other.compareMode) 856 904 return false; 857 if (key == null) { 858 if (other.key != null) 859 return false; 860 } else if (!key.equals(other.key)) 861 return false; 862 if (referenceNumber == null) { 863 if (other.referenceNumber != null) 864 return false; 865 } else if (!referenceNumber.equals(other.referenceNumber)) 866 return false; 867 if (referenceValue == null) { 868 if (other.referenceValue != null) 869 return false; 870 } else if (!referenceValue.equals(other.referenceValue)) 871 return false; 872 return true; 905 return Objects.equals(key, other.key) 906 && Objects.equals(referenceNumber, other.referenceNumber) 907 && Objects.equals(referenceValue, other.referenceValue); 873 908 } 874 909 … … 895 930 public static class ExactKeyValue extends TaggedMatch { 896 931 932 /** 933 * The mode to use for the comparison 934 */ 897 935 public enum Mode { 898 ANY, ANY_KEY, ANY_VALUE, EXACT, NONE, MISSING_KEY, 899 ANY_KEY_REGEXP, ANY_VALUE_REGEXP, EXACT_REGEXP, MISSING_KEY_REGEXP; 936 /** Matches everything */ 937 ANY, 938 /** Any key with the specified value will match */ 939 ANY_KEY, 940 /** Any value with the specified key will match */ 941 ANY_VALUE, 942 /** A key with the specified value will match */ 943 EXACT, 944 /** Nothing matches */ 945 NONE, 946 /** The key does not exist */ 947 MISSING_KEY, 948 /** Similar to {@link #ANY_KEY}, but the value matches a regex */ 949 ANY_KEY_REGEXP, 950 /** Similar to {@link #ANY_VALUE}, but the key matches a regex */ 951 ANY_VALUE_REGEXP, 952 /** Both the key and the value matches their respective regex */ 953 EXACT_REGEXP, 954 /** No key matching the regex exists */ 955 MISSING_KEY_REGEXP 900 956 } 901 957 … … 971 1027 return false; 972 1028 case MISSING_KEY: 973 return !osm.has Tag(key);1029 return !osm.hasKey(key); 974 1030 case ANY: 975 1031 return true; 976 1032 case ANY_VALUE: 977 return osm.has Tag(key);1033 return osm.hasKey(key); 978 1034 case ANY_KEY: 979 1035 return osm.getKeys().values().stream().anyMatch(v -> v.equals(value)); … … 1021 1077 return false; 1022 1078 ExactKeyValue other = (ExactKeyValue) obj; 1023 if (key == null) {1024 if (other.key != null)1025 return false;1026 } else if (!key.equals(other.key))1027 return false;1028 if (keyPattern == null) {1029 if (other.keyPattern != null)1030 return false;1031 } else if (!keyPattern.equals(other.keyPattern))1032 return false;1033 1079 if (mode != other.mode) 1034 1080 return false; 1035 if (value == null) { 1036 if (other.value != null) 1037 return false; 1038 } else if (!value.equals(other.value)) 1039 return false; 1040 if (valuePattern == null) { 1041 if (other.valuePattern != null) 1042 return false; 1043 } else if (!valuePattern.equals(other.valuePattern)) 1044 return false; 1045 return true; 1081 return Objects.equals(key, other.key) 1082 && Objects.equals(value, other.value) 1083 && Objects.equals(keyPattern, other.keyPattern) 1084 && Objects.equals(valuePattern, other.valuePattern); 1046 1085 } 1047 1086 } … … 1124 1163 if (caseSensitive != other.caseSensitive) 1125 1164 return false; 1126 if (search == null) { 1127 if (other.search != null) 1128 return false; 1129 } else if (!search.equals(other.search)) 1130 return false; 1131 if (searchRegex == null) { 1132 if (other.searchRegex != null) 1133 return false; 1134 } else if (!searchRegex.equals(other.searchRegex)) 1135 return false; 1136 return true; 1137 } 1138 } 1139 1165 return Objects.equals(search, other.search) 1166 && Objects.equals(searchRegex, other.searchRegex); 1167 } 1168 } 1169 1170 /** 1171 * Filter OsmPrimitives based off of the base primitive type 1172 */ 1140 1173 public static class ExactType extends Match { 1141 1174 private final OsmPrimitiveType type; … … 1220 1253 return false; 1221 1254 UserMatch other = (UserMatch) obj; 1222 if (user == null) { 1223 if (other.user != null) 1224 return false; 1225 } else if (!user.equals(other.user)) 1226 return false; 1227 return true; 1255 return Objects.equals(user, other.user); 1228 1256 } 1229 1257 } … … 1233 1261 */ 1234 1262 private static class RoleMatch extends Match { 1263 @Nonnull 1235 1264 private final String role; 1236 1265 … … 1259 1288 @Override 1260 1289 public int hashCode() { 1261 return 31 + ((role == null) ? 0 : role.hashCode());1290 return 31 + role.hashCode(); 1262 1291 } 1263 1292 … … 1269 1298 return false; 1270 1299 RoleMatch other = (RoleMatch) obj; 1271 if (role == null) { 1272 if (other.role != null) 1273 return false; 1274 } else if (!role.equals(other.role)) 1275 return false; 1276 return true; 1300 return role.equals(other.role); 1277 1301 } 1278 1302 } … … 1283 1307 private static class Nth extends Match { 1284 1308 1285 private final int nth ;1309 private final int nthObject; 1286 1310 private final boolean modulo; 1287 1311 … … 1291 1315 1292 1316 private Nth(int nth, boolean modulo) throws SearchParseError { 1293 this.nth = nth;1317 this.nthObject = nth; 1294 1318 this.modulo = modulo; 1295 if (this.modulo && this.nth == 0) {1319 if (this.modulo && this.nthObject == 0) { 1296 1320 throw new SearchParseError(tr("Non-zero integer expected")); 1297 1321 } … … 1314 1338 continue; 1315 1339 } 1316 if (nth < 0 && idx - maxIndex == nth) {1340 if (nthObject < 0 && idx - maxIndex == nthObject) { 1317 1341 return true; 1318 } else if (idx == nth || (modulo && idx % nth== 0))1342 } else if (idx == nthObject || (modulo && idx % nthObject == 0)) 1319 1343 return true; 1320 1344 } … … 1324 1348 @Override 1325 1349 public String toString() { 1326 return "Nth{nth=" + nth + ", modulo=" + modulo + '}';1350 return "Nth{nth=" + nthObject + ", modulo=" + modulo + '}'; 1327 1351 } 1328 1352 1329 1353 @Override 1330 1354 public int hashCode() { 1331 return Objects.hash(modulo, nth );1355 return Objects.hash(modulo, nthObject); 1332 1356 } 1333 1357 … … 1340 1364 Nth other = (Nth) obj; 1341 1365 return modulo == other.modulo 1342 && nth == other.nth;1366 && nthObject == other.nthObject; 1343 1367 } 1344 1368 } … … 1521 1545 final long maxDate; 1522 1546 try { 1523 // if min timestamp is empty: use lowest possible date1547 // if min timestamp is empty: use the lowest possible date 1524 1548 minDate = DateUtils.parseInstant(rangeA1.isEmpty() ? "1980" : rangeA1).toEpochMilli(); 1525 1549 } catch (UncheckedParseException | DateTimeException ex) { … … 1573 1597 return false; 1574 1598 HasRole other = (HasRole) obj; 1575 if (role == null) { 1576 if (other.role != null) 1577 return false; 1578 } else if (!role.equals(other.role)) 1579 return false; 1580 return true; 1599 return Objects.equals(role, other.role); 1581 1600 } 1582 1601 } … … 1644 1663 /** 1645 1664 * Match objects that are incomplete, where only id and type are known. 1646 * Typically some members of a relation are incomplete until they are1665 * Typically, some members of a relation are incomplete until they are 1647 1666 * fetched from the server. 1648 1667 */ … … 1865 1884 /** 1866 1885 * Matches objects which are not outside the source area ("downloaded area"). 1867 * Unlike {@link InDataSourceArea} this matches also if no source area is set (e.g., for new layers).1886 * Unlike {@link InDataSourceArea}, this matches also if no source area is set (e.g., for new layers). 1868 1887 */ 1869 1888 public static class NotOutsideDataSourceArea extends InDataSourceArea { … … 1955 1974 return false; 1956 1975 Preset other = (Preset) obj; 1957 if (presets == null) { 1958 if (other.presets != null) 1959 return false; 1960 } else if (!presets.equals(other.presets)) 1961 return false; 1962 return true; 1976 return Objects.equals(presets, other.presets); 1963 1977 } 1964 1978 } … … 2156 2170 // key:value form where value is a string (may be OSM key search) 2157 2171 final String value = tokenizer.readTextOrNumber(); 2158 return new KeyValue(key, value != null ? value : "", regexSearch, caseSensitive).validate(); 2172 if (value == null) { 2173 return new ExactKeyValue(regexSearch, caseSensitive, key, "*").validate(); 2174 } 2175 return new KeyValue(key, value, regexSearch, caseSensitive).validate(); 2159 2176 } else if (tokenizer.readIfEqual(Token.QUESTION_MARK)) 2160 2177 return new BooleanMatch(key, false); -
trunk/src/org/openstreetmap/josm/tools/SearchCompilerQueryWizard.java
r17988 r18847 38 38 public static String constructQuery(final String search) { 39 39 try { 40 Matcher matcher = Pattern.compile("\\s+GLOBAL\\s*$", Pattern.CASE_INSENSITIVE).matcher(search); 40 Matcher matcher = Pattern.compile("\\s+GLOBAL\\s*$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS) 41 .matcher(search); 41 42 if (matcher.find()) { 42 43 final Match match = SearchCompiler.compile(matcher.replaceFirst("")); … … 44 45 } 45 46 46 matcher = Pattern.compile("\\s+IN BBOX\\s*$", Pattern.CASE_INSENSITIVE).matcher(search); 47 matcher = Pattern.compile("\\s+IN BBOX\\s*$", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS) 48 .matcher(search); 47 49 if (matcher.find()) { 48 50 final Match match = SearchCompiler.compile(matcher.replaceFirst("")); … … 50 52 } 51 53 52 matcher = Pattern.compile("\\s+(?<mode>IN|AROUND)\\s+(?<area>[^\" ]+|\"[^\"]+\")\\s*$", Pattern.CASE_INSENSITIVE).matcher(search); 54 matcher = Pattern.compile("\\s+(?<mode>IN|AROUND)\\s+(?<area>[^\" ]+|\"[^\"]+\")\\s*$", 55 Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS).matcher(search); 53 56 if (matcher.find()) { 54 57 final Match match = SearchCompiler.compile(matcher.replaceFirst("")); … … 79 82 final EnumSet<OsmPrimitiveType> types = EnumSet.noneOf(OsmPrimitiveType.class); 80 83 final String query = constructQuery(conjunction, types); 81 (types.isEmpty() || types.size() == 384 queryLines.addAll((types.isEmpty() || types.size() == 3 82 85 ? Stream.of("nwr") 83 86 : types.stream().map(OsmPrimitiveType::getAPIName)) 84 .forEach(type -> queryLines.add(" " + type + query + queryLineSuffix + ";")); 87 .map(type -> " " + type + query + queryLineSuffix + ";") 88 .collect(Collectors.toList())); 85 89 } 86 90 queryLines.add(");");
Note:
See TracChangeset
for help on using the changeset viewer.