source: josm/trunk/src/org/openstreetmap/josm/io/ChangesetQuery.java@ 8510

Last change on this file since 8510 was 8510, checked in by Don-vip, 9 years ago

checkstyle: enable relevant whitespace checks and fix them

  • Property svn:eol-style set to native
File size: 20.5 KB
RevLine 
[2512]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
[2688]4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.DateFormat;
[2852]7import java.text.MessageFormat;
[2688]8import java.text.ParseException;
[6440]9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
[2512]12import java.util.Date;
[2688]13import java.util.HashMap;
[6440]14import java.util.HashSet;
[2688]15import java.util.Map;
[6258]16import java.util.Map.Entry;
[2512]17
[2688]18import org.openstreetmap.josm.data.Bounds;
[2512]19import org.openstreetmap.josm.data.coor.LatLon;
[2688]20import org.openstreetmap.josm.tools.CheckParameterUtil;
[6440]21import org.openstreetmap.josm.tools.Utils;
[7299]22import org.openstreetmap.josm.tools.date.DateUtils;
[2512]23
[2688]24public class ChangesetQuery {
[2512]25
[2688]26 /**
[7012]27 * Replies a changeset query object from the query part of a OSM API URL for querying changesets.
[2711]28 *
[2688]29 * @param query the query part
30 * @return the query object
[8291]31 * @throws ChangesetQueryUrlException if query doesn't consist of valid query parameters
[2688]32 */
[8510]33 public static ChangesetQuery buildFromUrlQuery(String query) throws ChangesetQueryUrlException {
[2688]34 return new ChangesetQueryUrlParser().parse(query);
35 }
36
37 /** the user id this query is restricted to. null, if no restriction to a user id applies */
38 private Integer uid = null;
39 /** the user name this query is restricted to. null, if no restriction to a user name applies */
40 private String userName = null;
41 /** the bounding box this query is restricted to. null, if no restriction to a bounding box applies */
42 private Bounds bounds = null;
43
[2512]44 private Date closedAfter = null;
45 private Date createdBefore = null;
[2688]46 /** indicates whether only open changesets are queried. null, if no restrictions regarding open changesets apply */
[2512]47 private Boolean open = null;
[2688]48 /** indicates whether only closed changesets are queried. null, if no restrictions regarding open changesets apply */
[2512]49 private Boolean closed = null;
[6440]50 /** a collection of changeset ids to query for */
51 private Collection<Long> changesetIds = null;
[2512]52
[6830]53 /**
[2688]54 * Restricts the query to changesets owned by the user with id <code>uid</code>.
[2711]55 *
[6830]56 * @param uid the uid of the user. &gt; 0 expected.
[2688]57 * @return the query object with the applied restriction
[8291]58 * @throws IllegalArgumentException if uid &lt;= 0
[2688]59 * @see #forUser(String)
60 */
[8291]61 public ChangesetQuery forUser(int uid) {
[2512]62 if (uid <= 0)
[2852]63 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "uid", uid));
[2688]64 this.uid = uid;
65 this.userName = null;
[2512]66 return this;
67 }
68
[2688]69 /**
70 * Restricts the query to changesets owned by the user with user name <code>username</code>.
[2711]71 *
[2688]72 * Caveat: for historical reasons the username might not be unique! It is recommended to use
[5266]73 * {@link #forUser(int)} to restrict the query to a specific user.
[2711]74 *
[2688]75 * @param username the username. Must not be null.
76 * @return the query object with the applied restriction
[8291]77 * @throws IllegalArgumentException if username is null.
[2688]78 * @see #forUser(int)
79 */
80 public ChangesetQuery forUser(String username) {
81 CheckParameterUtil.ensureParameterNotNull(username, "username");
82 this.userName = username;
[4902]83 this.uid = null;
[2688]84 return this;
85 }
86
87 /**
[7299]88 * Replies true if this query is restricted to user whom we only know the user name for.
[2711]89 *
[7299]90 * @return true if this query is restricted to user whom we only know the user name for
[2688]91 */
92 public boolean isRestrictedToPartiallyIdentifiedUser() {
93 return userName != null;
94 }
95
96 /**
97 * Replies the user name which this query is restricted to. null, if this query isn't
[5266]98 * restricted to a user name, i.e. if {@link #isRestrictedToPartiallyIdentifiedUser()} is false.
[2711]99 *
[2688]100 * @return the user name which this query is restricted to
101 */
102 public String getUserName() {
103 return userName;
104 }
105
106 /**
107 * Replies true if this query is restricted to user whom know the user id for.
[2711]108 *
[2688]109 * @return true if this query is restricted to user whom know the user id for
110 */
111 public boolean isRestrictedToFullyIdentifiedUser() {
112 return uid > 0;
113 }
114
115 /**
116 * Replies a query which is restricted to a bounding box.
[2711]117 *
[2688]118 * @param minLon min longitude of the bounding box. Valid longitude value expected.
119 * @param minLat min latitude of the bounding box. Valid latitude value expected.
120 * @param maxLon max longitude of the bounding box. Valid longitude value expected.
121 * @param maxLat max latitude of the bounding box. Valid latitude value expected.
[2711]122 *
[2688]123 * @return the restricted changeset query
[8291]124 * @throws IllegalArgumentException if either of the parameters isn't a valid longitude or
[2688]125 * latitude value
126 */
[8291]127 public ChangesetQuery inBbox(double minLon, double minLat, double maxLon, double maxLat) {
[2688]128 if (!LatLon.isValidLon(minLon))
129 throw new IllegalArgumentException(tr("Illegal longitude value for parameter ''{0}'', got {1}", "minLon", minLon));
130 if (!LatLon.isValidLon(maxLon))
131 throw new IllegalArgumentException(tr("Illegal longitude value for parameter ''{0}'', got {1}", "maxLon", maxLon));
132 if (!LatLon.isValidLat(minLat))
133 throw new IllegalArgumentException(tr("Illegal latitude value for parameter ''{0}'', got {1}", "minLat", minLat));
134 if (!LatLon.isValidLat(maxLat))
135 throw new IllegalArgumentException(tr("Illegal longitude value for parameter ''{0}'', got {1}", "maxLat", maxLat));
136
[2512]137 return inBbox(new LatLon(minLon, minLat), new LatLon(maxLon, maxLat));
138 }
139
[2688]140 /**
141 * Replies a query which is restricted to a bounding box.
[2711]142 *
[2688]143 * @param min the min lat/lon coordinates of the bounding box. Must not be null.
144 * @param max the max lat/lon coordiantes of the bounding box. Must not be null.
[2711]145 *
[2688]146 * @return the restricted changeset query
[8291]147 * @throws IllegalArgumentException if min is null
148 * @throws IllegalArgumentException if max is null
[2688]149 */
[2512]150 public ChangesetQuery inBbox(LatLon min, LatLon max) {
[2688]151 CheckParameterUtil.ensureParameterNotNull(min, "min");
152 CheckParameterUtil.ensureParameterNotNull(max, "max");
[8510]153 this.bounds = new Bounds(min, max);
[2512]154 return this;
155 }
156
[2688]157 /**
158 * Replies a query which is restricted to a bounding box given by <code>bbox</code>.
[2711]159 *
[2688]160 * @param bbox the bounding box. Must not be null.
161 * @return the changeset query
[8291]162 * @throws IllegalArgumentException if bbox is null.
[2688]163 */
[8291]164 public ChangesetQuery inBbox(Bounds bbox) {
[2688]165 CheckParameterUtil.ensureParameterNotNull(bbox, "bbox");
166 this.bounds = bbox;
167 return this;
168 }
169
170 /**
171 * Restricts the result to changesets which have been closed after the date given by <code>d</code>.
172 * <code>d</code> d is a date relative to the current time zone.
[2711]173 *
[2688]174 * @param d the date . Must not be null.
175 * @return the restricted changeset query
[8291]176 * @throws IllegalArgumentException if d is null
[2688]177 */
[8291]178 public ChangesetQuery closedAfter(Date d) {
[2688]179 CheckParameterUtil.ensureParameterNotNull(d, "d");
[2512]180 this.closedAfter = d;
181 return this;
182 }
183
[2688]184 /**
185 * Restricts the result to changesets which have been closed after <code>closedAfter</code> and which
186 * habe been created before <code>createdBefore</code>. Both dates are expressed relative to the current
187 * time zone.
[2711]188 *
[2688]189 * @param closedAfter only reply changesets closed after this date. Must not be null.
190 * @param createdBefore only reply changesets created before this date. Must not be null.
191 * @return the restricted changeset query
[8291]192 * @throws IllegalArgumentException if closedAfter is null
193 * @throws IllegalArgumentException if createdBefore is null
[2688]194 */
[8443]195 public ChangesetQuery closedAfterAndCreatedBefore(Date closedAfter, Date createdBefore) {
[2688]196 CheckParameterUtil.ensureParameterNotNull(closedAfter, "closedAfter");
197 CheckParameterUtil.ensureParameterNotNull(createdBefore, "createdBefore");
[2512]198 this.closedAfter = closedAfter;
199 this.createdBefore = createdBefore;
200 return this;
201 }
202
[2688]203 /**
204 * Restricts the result to changesets which are or aren't open, depending on the value of
205 * <code>isOpen</code>
[2711]206 *
[2688]207 * @param isOpen whether changesets should or should not be open
208 * @return the restricted changeset query
209 */
210 public ChangesetQuery beingOpen(boolean isOpen) {
211 this.open = isOpen;
[2512]212 return this;
213 }
214
[2688]215 /**
216 * Restricts the result to changesets which are or aren't closed, depending on the value of
217 * <code>isClosed</code>
[2711]218 *
[2688]219 * @param isClosed whether changesets should or should not be open
220 * @return the restricted changeset query
221 */
222 public ChangesetQuery beingClosed(boolean isClosed) {
223 this.closed = isClosed;
[2512]224 return this;
225 }
226
[2688]227 /**
[6440]228 * Restricts the query to the given changeset ids (which are added to previously added ones).
229 *
230 * @param changesetIds the changeset ids
231 * @return the query object with the applied restriction
[8291]232 * @throws IllegalArgumentException if changesetIds is null.
[6440]233 */
234 public ChangesetQuery forChangesetIds(Collection<Long> changesetIds) {
235 CheckParameterUtil.ensureParameterNotNull(changesetIds, "changesetIds");
236 this.changesetIds = changesetIds;
237 return this;
238 }
239
240 /**
[2688]241 * Replies the query string to be used in a query URL for the OSM API.
[2711]242 *
[2688]243 * @return the query string
244 */
[2512]245 public String getQueryString() {
[6822]246 StringBuilder sb = new StringBuilder();
[2688]247 if (uid != null) {
[8390]248 sb.append("user=").append(uid);
[2688]249 } else if (userName != null) {
[8390]250 sb.append("display_name=").append(Utils.encodeUrl(userName));
[2512]251 }
[2688]252 if (bounds != null) {
[2512]253 if (sb.length() > 0) {
[8390]254 sb.append('&');
[2512]255 }
[2688]256 sb.append("bbox=").append(bounds.encodeAsString(","));
[2512]257 }
258 if (closedAfter != null && createdBefore != null) {
259 if (sb.length() > 0) {
[8390]260 sb.append('&');
[2512]261 }
[7299]262 DateFormat df = DateUtils.newIsoDateTimeFormat();
[8390]263 sb.append("time=").append(df.format(closedAfter));
264 sb.append(',').append(df.format(createdBefore));
[2512]265 } else if (closedAfter != null) {
266 if (sb.length() > 0) {
[8390]267 sb.append('&');
[2512]268 }
[7299]269 DateFormat df = DateUtils.newIsoDateTimeFormat();
[8390]270 sb.append("time=").append(df.format(closedAfter));
[2512]271 }
272
273 if (open != null) {
274 if (sb.length() > 0) {
[8390]275 sb.append('&');
[2512]276 }
[2688]277 sb.append("open=").append(Boolean.toString(open));
[2512]278 } else if (closed != null) {
279 if (sb.length() > 0) {
[8390]280 sb.append('&');
[2512]281 }
[2688]282 sb.append("closed=").append(Boolean.toString(closed));
[6440]283 } else if (changesetIds != null) {
284 // since 2013-12-05, see https://github.com/openstreetmap/openstreetmap-website/commit/1d1f194d598e54a5d6fb4f38fb569d4138af0dc8
285 if (sb.length() > 0) {
[8390]286 sb.append('&');
[6440]287 }
288 sb.append("changesets=").append(Utils.join(",", changesetIds));
[2512]289 }
290 return sb.toString();
291 }
[2688]292
[5366]293 @Override
294 public String toString() {
295 return getQueryString();
296 }
297
[2688]298 public static class ChangesetQueryUrlException extends Exception {
299
[7299]300 /**
301 * Constructs a new {@code ChangesetQueryUrlException} with the specified detail message.
302 *
303 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
304 */
305 public ChangesetQueryUrlException(String message) {
306 super(message);
[2688]307 }
308
[7299]309 /**
[8394]310 * Constructs a new {@code ChangesetQueryUrlException} with the specified cause and detail message.
311 *
312 * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
313 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
314 * (A <tt>null</tt> value is permitted, and indicates that the cause is nonexistent or unknown.)
315 */
316 public ChangesetQueryUrlException(String message, Throwable cause) {
317 super(message, cause);
318 }
319
320 /**
[7299]321 * Constructs a new {@code ChangesetQueryUrlException} with the specified cause and a detail message of
322 * <tt>(cause==null ? null : cause.toString())</tt> (which typically contains the class and detail message of <tt>cause</tt>).
323 *
324 * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
325 * (A <tt>null</tt> value is permitted, and indicates that the cause is nonexistent or unknown.)
326 */
327 public ChangesetQueryUrlException(Throwable cause) {
328 super(cause);
[2688]329 }
330 }
331
332 public static class ChangesetQueryUrlParser {
333 protected int parseUid(String value) throws ChangesetQueryUrlException {
[6087]334 if (value == null || value.trim().isEmpty())
[8394]335 throw new ChangesetQueryUrlException(
336 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value));
[2688]337 int id;
338 try {
339 id = Integer.parseInt(value);
340 if (id <= 0)
[8394]341 throw new ChangesetQueryUrlException(
342 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value));
[8510]343 } catch (NumberFormatException e) {
[8394]344 throw new ChangesetQueryUrlException(
345 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value), e);
[2688]346 }
347 return id;
348 }
349
350 protected boolean parseBoolean(String value, String parameter) throws ChangesetQueryUrlException {
[6087]351 if (value == null || value.trim().isEmpty())
[8394]352 throw new ChangesetQueryUrlException(
353 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value));
[7012]354 switch (value) {
355 case "true":
[2688]356 return true;
[7012]357 case "false":
[2688]358 return false;
[7012]359 default:
[8394]360 throw new ChangesetQueryUrlException(
361 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value));
[7012]362 }
[2688]363 }
364
365 protected Date parseDate(String value, String parameter) throws ChangesetQueryUrlException {
[6087]366 if (value == null || value.trim().isEmpty())
[8394]367 throw new ChangesetQueryUrlException(
368 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value));
[7299]369 DateFormat formatter = DateUtils.newIsoDateTimeFormat();
[2688]370 try {
371 return formatter.parse(value);
[8510]372 } catch (ParseException e) {
[8394]373 throw new ChangesetQueryUrlException(
374 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value), e);
[2688]375 }
376 }
377
378 protected Date[] parseTime(String value) throws ChangesetQueryUrlException {
379 String[] dates = value.split(",");
380 if (dates == null || dates.length == 0 || dates.length > 2)
[8394]381 throw new ChangesetQueryUrlException(
382 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "time", value));
[2688]383 if (dates.length == 1)
384 return new Date[]{parseDate(dates[0], "time")};
385 else if (dates.length == 2)
[7299]386 return new Date[]{parseDate(dates[0], "time"), parseDate(dates[1], "time")};
[2688]387 return null;
388 }
389
[6440]390 protected Collection<Long> parseLongs(String value) {
391 return value == null || value.isEmpty()
392 ? Collections.<Long>emptySet() :
[7005]393 new HashSet<>(Utils.transform(Arrays.asList(value.split(",")), new Utils.Function<String, Long>() {
[6440]394 @Override
395 public Long apply(String x) {
396 return Long.valueOf(x);
397 }
398 }));
399 }
400
401 protected ChangesetQuery createFromMap(Map<String, String> queryParams) throws ChangesetQueryUrlException {
[2688]402 ChangesetQuery csQuery = new ChangesetQuery();
403
[6258]404 for (Entry<String, String> entry: queryParams.entrySet()) {
405 String k = entry.getKey();
[7012]406 switch(k) {
407 case "uid":
[2688]408 if (queryParams.containsKey("display_name"))
[8394]409 throw new ChangesetQueryUrlException(
410 tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
[2688]411 csQuery.forUser(parseUid(queryParams.get("uid")));
[7012]412 break;
413 case "display_name":
[2688]414 if (queryParams.containsKey("uid"))
[8394]415 throw new ChangesetQueryUrlException(
416 tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
[2688]417 csQuery.forUser(queryParams.get("display_name"));
[7012]418 break;
419 case "open":
420 csQuery.beingOpen(parseBoolean(entry.getValue(), "open"));
421 break;
422 case "closed":
423 csQuery.beingClosed(parseBoolean(entry.getValue(), "closed"));
424 break;
425 case "time":
[6258]426 Date[] dates = parseTime(entry.getValue());
[2688]427 switch(dates.length) {
428 case 1:
429 csQuery.closedAfter(dates[0]);
430 break;
431 case 2:
432 csQuery.closedAfterAndCreatedBefore(dates[0], dates[1]);
433 break;
434 }
[7012]435 break;
436 case "bbox":
[2688]437 try {
[6258]438 csQuery.inBbox(new Bounds(entry.getValue(), ","));
[7299]439 } catch (IllegalArgumentException e) {
[2688]440 throw new ChangesetQueryUrlException(e);
441 }
[7012]442 break;
443 case "changesets":
[6440]444 try {
445 csQuery.forChangesetIds(parseLongs(entry.getValue()));
446 } catch (NumberFormatException e) {
447 throw new ChangesetQueryUrlException(e);
448 }
[7012]449 break;
450 default:
[8394]451 throw new ChangesetQueryUrlException(
452 tr("Unsupported parameter ''{0}'' in changeset query string", k));
[7012]453 }
[2688]454 }
455 return csQuery;
456 }
457
[8510]458 protected Map<String, String> createMapFromQueryString(String query) {
459 Map<String, String> queryParams = new HashMap<>();
[2688]460 String[] keyValuePairs = query.split("&");
461 for (String keyValuePair: keyValuePairs) {
462 String[] kv = keyValuePair.split("=");
[6440]463 queryParams.put(kv[0], kv.length > 1 ? kv[1] : "");
[2688]464 }
465 return queryParams;
466 }
467
468 /**
[6830]469 * Parses the changeset query given as URL query parameters and replies a {@link ChangesetQuery}.
[2711]470 *
[2688]471 * <code>query</code> is the query part of a API url for querying changesets,
472 * see <a href="http://wiki.openstreetmap.org/wiki/API_v0.6#Query:_GET_.2Fapi.2F0.6.2Fchangesets">OSM API</a>.
[2711]473 *
[2688]474 * Example for an query string:<br>
475 * <pre>
[6830]476 * uid=1234&amp;open=true
[2688]477 * </pre>
[2711]478 *
[2688]479 * @param query the query string. If null, an empty query (identical to a query for all changesets) is
480 * assumed
481 * @return the changeset query
482 * @throws ChangesetQueryUrlException if the query string doesn't represent a legal query for changesets
483 */
[7299]484 public ChangesetQuery parse(String query) throws ChangesetQueryUrlException {
[2688]485 if (query == null)
486 return new ChangesetQuery();
487 query = query.trim();
[6087]488 if (query.isEmpty())
[2688]489 return new ChangesetQuery();
[8510]490 Map<String, String> queryParams = createMapFromQueryString(query);
[6440]491 return createFromMap(queryParams);
[2688]492 }
493 }
[2512]494}
Note: See TracBrowser for help on using the repository browser.