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

Last change on this file since 7276 was 7012, checked in by Don-vip, 10 years ago

see #8465 - use String switch/case where applicable

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