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

Last change on this file since 8764 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
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.text.DateFormat;
7import java.text.MessageFormat;
8import java.text.ParseException;
9import java.util.Arrays;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.Date;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.Map;
16import java.util.Map.Entry;
17
18import org.openstreetmap.josm.data.Bounds;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.tools.CheckParameterUtil;
21import org.openstreetmap.josm.tools.Utils;
22import org.openstreetmap.josm.tools.date.DateUtils;
23
24public class ChangesetQuery {
25
26 /**
27 * Replies a changeset query object from the query part of a OSM API URL for querying changesets.
28 *
29 * @param query the query part
30 * @return the query object
31 * @throws ChangesetQueryUrlException if query doesn't consist of valid query parameters
32 */
33 public static ChangesetQuery buildFromUrlQuery(String query) throws ChangesetQueryUrlException {
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
44 private Date closedAfter = null;
45 private Date createdBefore = null;
46 /** indicates whether only open changesets are queried. null, if no restrictions regarding open changesets apply */
47 private Boolean open = null;
48 /** indicates whether only closed changesets are queried. null, if no restrictions regarding open changesets apply */
49 private Boolean closed = null;
50 /** a collection of changeset ids to query for */
51 private Collection<Long> changesetIds = null;
52
53 /**
54 * Restricts the query to changesets owned by the user with id <code>uid</code>.
55 *
56 * @param uid the uid of the user. &gt; 0 expected.
57 * @return the query object with the applied restriction
58 * @throws IllegalArgumentException if uid &lt;= 0
59 * @see #forUser(String)
60 */
61 public ChangesetQuery forUser(int uid) {
62 if (uid <= 0)
63 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "uid", uid));
64 this.uid = uid;
65 this.userName = null;
66 return this;
67 }
68
69 /**
70 * Restricts the query to changesets owned by the user with user name <code>username</code>.
71 *
72 * Caveat: for historical reasons the username might not be unique! It is recommended to use
73 * {@link #forUser(int)} to restrict the query to a specific user.
74 *
75 * @param username the username. Must not be null.
76 * @return the query object with the applied restriction
77 * @throws IllegalArgumentException if username is null.
78 * @see #forUser(int)
79 */
80 public ChangesetQuery forUser(String username) {
81 CheckParameterUtil.ensureParameterNotNull(username, "username");
82 this.userName = username;
83 this.uid = null;
84 return this;
85 }
86
87 /**
88 * Replies true if this query is restricted to user whom we only know the user name for.
89 *
90 * @return true if this query is restricted to user whom we only know the user name for
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
98 * restricted to a user name, i.e. if {@link #isRestrictedToPartiallyIdentifiedUser()} is false.
99 *
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.
108 *
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.
117 *
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.
122 *
123 * @return the restricted changeset query
124 * @throws IllegalArgumentException if either of the parameters isn't a valid longitude or
125 * latitude value
126 */
127 public ChangesetQuery inBbox(double minLon, double minLat, double maxLon, double maxLat) {
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
137 return inBbox(new LatLon(minLon, minLat), new LatLon(maxLon, maxLat));
138 }
139
140 /**
141 * Replies a query which is restricted to a bounding box.
142 *
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.
145 *
146 * @return the restricted changeset query
147 * @throws IllegalArgumentException if min is null
148 * @throws IllegalArgumentException if max is null
149 */
150 public ChangesetQuery inBbox(LatLon min, LatLon max) {
151 CheckParameterUtil.ensureParameterNotNull(min, "min");
152 CheckParameterUtil.ensureParameterNotNull(max, "max");
153 this.bounds = new Bounds(min, max);
154 return this;
155 }
156
157 /**
158 * Replies a query which is restricted to a bounding box given by <code>bbox</code>.
159 *
160 * @param bbox the bounding box. Must not be null.
161 * @return the changeset query
162 * @throws IllegalArgumentException if bbox is null.
163 */
164 public ChangesetQuery inBbox(Bounds bbox) {
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.
173 *
174 * @param d the date . Must not be null.
175 * @return the restricted changeset query
176 * @throws IllegalArgumentException if d is null
177 */
178 public ChangesetQuery closedAfter(Date d) {
179 CheckParameterUtil.ensureParameterNotNull(d, "d");
180 this.closedAfter = d;
181 return this;
182 }
183
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.
188 *
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
192 * @throws IllegalArgumentException if closedAfter is null
193 * @throws IllegalArgumentException if createdBefore is null
194 */
195 public ChangesetQuery closedAfterAndCreatedBefore(Date closedAfter, Date createdBefore) {
196 CheckParameterUtil.ensureParameterNotNull(closedAfter, "closedAfter");
197 CheckParameterUtil.ensureParameterNotNull(createdBefore, "createdBefore");
198 this.closedAfter = closedAfter;
199 this.createdBefore = createdBefore;
200 return this;
201 }
202
203 /**
204 * Restricts the result to changesets which are or aren't open, depending on the value of
205 * <code>isOpen</code>
206 *
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;
212 return this;
213 }
214
215 /**
216 * Restricts the result to changesets which are or aren't closed, depending on the value of
217 * <code>isClosed</code>
218 *
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;
224 return this;
225 }
226
227 /**
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
232 * @throws IllegalArgumentException if changesetIds is null.
233 */
234 public ChangesetQuery forChangesetIds(Collection<Long> changesetIds) {
235 CheckParameterUtil.ensureParameterNotNull(changesetIds, "changesetIds");
236 this.changesetIds = changesetIds;
237 return this;
238 }
239
240 /**
241 * Replies the query string to be used in a query URL for the OSM API.
242 *
243 * @return the query string
244 */
245 public String getQueryString() {
246 StringBuilder sb = new StringBuilder();
247 if (uid != null) {
248 sb.append("user=").append(uid);
249 } else if (userName != null) {
250 sb.append("display_name=").append(Utils.encodeUrl(userName));
251 }
252 if (bounds != null) {
253 if (sb.length() > 0) {
254 sb.append('&');
255 }
256 sb.append("bbox=").append(bounds.encodeAsString(","));
257 }
258 if (closedAfter != null && createdBefore != null) {
259 if (sb.length() > 0) {
260 sb.append('&');
261 }
262 DateFormat df = DateUtils.newIsoDateTimeFormat();
263 sb.append("time=").append(df.format(closedAfter));
264 sb.append(',').append(df.format(createdBefore));
265 } else if (closedAfter != null) {
266 if (sb.length() > 0) {
267 sb.append('&');
268 }
269 DateFormat df = DateUtils.newIsoDateTimeFormat();
270 sb.append("time=").append(df.format(closedAfter));
271 }
272
273 if (open != null) {
274 if (sb.length() > 0) {
275 sb.append('&');
276 }
277 sb.append("open=").append(Boolean.toString(open));
278 } else if (closed != null) {
279 if (sb.length() > 0) {
280 sb.append('&');
281 }
282 sb.append("closed=").append(Boolean.toString(closed));
283 } else if (changesetIds != null) {
284 // since 2013-12-05, see https://github.com/openstreetmap/openstreetmap-website/commit/1d1f194d598e54a5d6fb4f38fb569d4138af0dc8
285 if (sb.length() > 0) {
286 sb.append('&');
287 }
288 sb.append("changesets=").append(Utils.join(",", changesetIds));
289 }
290 return sb.toString();
291 }
292
293 @Override
294 public String toString() {
295 return getQueryString();
296 }
297
298 public static class ChangesetQueryUrlException extends Exception {
299
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);
307 }
308
309 /**
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 /**
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);
329 }
330 }
331
332 public static class ChangesetQueryUrlParser {
333 protected int parseUid(String value) throws ChangesetQueryUrlException {
334 if (value == null || value.trim().isEmpty())
335 throw new ChangesetQueryUrlException(
336 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value));
337 int id;
338 try {
339 id = Integer.parseInt(value);
340 if (id <= 0)
341 throw new ChangesetQueryUrlException(
342 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value));
343 } catch (NumberFormatException e) {
344 throw new ChangesetQueryUrlException(
345 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid", value), e);
346 }
347 return id;
348 }
349
350 protected boolean parseBoolean(String value, String parameter) throws ChangesetQueryUrlException {
351 if (value == null || value.trim().isEmpty())
352 throw new ChangesetQueryUrlException(
353 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value));
354 switch (value) {
355 case "true":
356 return true;
357 case "false":
358 return false;
359 default:
360 throw new ChangesetQueryUrlException(
361 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value));
362 }
363 }
364
365 protected Date parseDate(String value, String parameter) throws ChangesetQueryUrlException {
366 if (value == null || value.trim().isEmpty())
367 throw new ChangesetQueryUrlException(
368 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value));
369 DateFormat formatter = DateUtils.newIsoDateTimeFormat();
370 try {
371 return formatter.parse(value);
372 } catch (ParseException e) {
373 throw new ChangesetQueryUrlException(
374 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter, value), e);
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)
381 throw new ChangesetQueryUrlException(
382 tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "time", value));
383 if (dates.length == 1)
384 return new Date[]{parseDate(dates[0], "time")};
385 else if (dates.length == 2)
386 return new Date[]{parseDate(dates[0], "time"), parseDate(dates[1], "time")};
387 return null;
388 }
389
390 protected Collection<Long> parseLongs(String value) {
391 return value == null || value.isEmpty()
392 ? Collections.<Long>emptySet() :
393 new HashSet<>(Utils.transform(Arrays.asList(value.split(",")), new Utils.Function<String, Long>() {
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 {
402 ChangesetQuery csQuery = new ChangesetQuery();
403
404 for (Entry<String, String> entry: queryParams.entrySet()) {
405 String k = entry.getKey();
406 switch(k) {
407 case "uid":
408 if (queryParams.containsKey("display_name"))
409 throw new ChangesetQueryUrlException(
410 tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
411 csQuery.forUser(parseUid(queryParams.get("uid")));
412 break;
413 case "display_name":
414 if (queryParams.containsKey("uid"))
415 throw new ChangesetQueryUrlException(
416 tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
417 csQuery.forUser(queryParams.get("display_name"));
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":
426 Date[] dates = parseTime(entry.getValue());
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 }
435 break;
436 case "bbox":
437 try {
438 csQuery.inBbox(new Bounds(entry.getValue(), ","));
439 } catch (IllegalArgumentException e) {
440 throw new ChangesetQueryUrlException(e);
441 }
442 break;
443 case "changesets":
444 try {
445 csQuery.forChangesetIds(parseLongs(entry.getValue()));
446 } catch (NumberFormatException e) {
447 throw new ChangesetQueryUrlException(e);
448 }
449 break;
450 default:
451 throw new ChangesetQueryUrlException(
452 tr("Unsupported parameter ''{0}'' in changeset query string", k));
453 }
454 }
455 return csQuery;
456 }
457
458 protected Map<String, String> createMapFromQueryString(String query) {
459 Map<String, String> queryParams = new HashMap<>();
460 String[] keyValuePairs = query.split("&");
461 for (String keyValuePair: keyValuePairs) {
462 String[] kv = keyValuePair.split("=");
463 queryParams.put(kv[0], kv.length > 1 ? kv[1] : "");
464 }
465 return queryParams;
466 }
467
468 /**
469 * Parses the changeset query given as URL query parameters and replies a {@link ChangesetQuery}.
470 *
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>.
473 *
474 * Example for an query string:<br>
475 * <pre>
476 * uid=1234&amp;open=true
477 * </pre>
478 *
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 */
484 public ChangesetQuery parse(String query) throws ChangesetQueryUrlException {
485 if (query == null)
486 return new ChangesetQuery();
487 query = query.trim();
488 if (query.isEmpty())
489 return new ChangesetQuery();
490 Map<String, String> queryParams = createMapFromQueryString(query);
491 return createFromMap(queryParams);
492 }
493 }
494}
Note: See TracBrowser for help on using the repository browser.