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

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

global replacement of e.printStackTrace() by Main.error(e)

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