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

Last change on this file since 5903 was 5366, checked in by Don-vip, 12 years ago

URL-encode user name when querying changesets in order to download changesets of deleted OSM user accounts such as "huohw%5pqefhewq[ojmfq]w"

  • Property svn:eol-style set to native
File size: 18.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.Date;
13import java.util.HashMap;
14import java.util.Map;
15
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.tools.CheckParameterUtil;
19
20public class ChangesetQuery {
21
22 /**
23 * Replies a changeset query object from the query part of a OSM API URL for querying
24 * changesets.
25 *
26 * @param query the query part
27 * @return the query object
28 * @throws ChangesetQueryUrlException thrown if query doesn't consist of valid query parameters
29 *
30 */
31 static public ChangesetQuery buildFromUrlQuery(String query) throws ChangesetQueryUrlException{
32 return new ChangesetQueryUrlParser().parse(query);
33 }
34
35 /** the user id this query is restricted to. null, if no restriction to a user id applies */
36 private Integer uid = null;
37 /** the user name this query is restricted to. null, if no restriction to a user name applies */
38 private String userName = null;
39 /** the bounding box this query is restricted to. null, if no restriction to a bounding box applies */
40 private Bounds bounds = null;
41
42 private Date closedAfter = null;
43 private Date createdBefore = null;
44 /** indicates whether only open changesets are queried. null, if no restrictions regarding open changesets apply */
45 private Boolean open = null;
46 /** indicates whether only closed changesets are queried. null, if no restrictions regarding open changesets apply */
47 private Boolean closed = null;
48
49 public ChangesetQuery() {}
50
51 /**
52 * Restricts the query to changesets owned by the user with id <code>uid</code>.
53 *
54 * @param uid the uid of the user. >0 expected.
55 * @return the query object with the applied restriction
56 * @throws IllegalArgumentException thrown if uid <= 0
57 * @see #forUser(String)
58 */
59 public ChangesetQuery forUser(int uid) throws IllegalArgumentException{
60 if (uid <= 0)
61 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "uid", uid));
62 this.uid = uid;
63 this.userName = null;
64 return this;
65 }
66
67 /**
68 * Restricts the query to changesets owned by the user with user name <code>username</code>.
69 *
70 * Caveat: for historical reasons the username might not be unique! It is recommended to use
71 * {@link #forUser(int)} to restrict the query to a specific user.
72 *
73 * @param username the username. Must not be null.
74 * @return the query object with the applied restriction
75 * @throws IllegalArgumentException thrown if username is null.
76 * @see #forUser(int)
77 */
78 public ChangesetQuery forUser(String username) {
79 CheckParameterUtil.ensureParameterNotNull(username, "username");
80 this.userName = username;
81 this.uid = null;
82 return this;
83 }
84
85 /**
86 * Replies true if this query is restricted to user whom we only know the user name
87 * for.
88 *
89 * @return true if this query is restricted to user whom we only know the user name
90 * 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 thrown 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) throws IllegalArgumentException{
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 thrown if min is null
148 * @throws IllegalArgumentException thrown 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 thrown if bbox is null.
163 */
164 public ChangesetQuery inBbox(Bounds bbox) throws IllegalArgumentException {
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 thrown if d is null
177 */
178 public ChangesetQuery closedAfter(Date d) throws IllegalArgumentException{
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 thrown if closedAfter is null
193 * @throws IllegalArgumentException thrown if createdBefore is null
194 */
195 public ChangesetQuery closedAfterAndCreatedBefore(Date closedAfter, Date createdBefore ) throws IllegalArgumentException{
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 * Replies the query string to be used in a query URL for the OSM API.
229 *
230 * @return the query string
231 */
232 public String getQueryString() {
233 StringBuffer sb = new StringBuffer();
234 if (uid != null) {
235 sb.append("user").append("=").append(uid);
236 } else if (userName != null) {
237 try {
238 sb.append("display_name").append("=").append(URLEncoder.encode(userName, "UTF-8"));
239 } catch (UnsupportedEncodingException e) {
240 e.printStackTrace();
241 }
242 }
243 if (bounds != null) {
244 if (sb.length() > 0) {
245 sb.append("&");
246 }
247 sb.append("bbox=").append(bounds.encodeAsString(","));
248 }
249 if (closedAfter != null && createdBefore != null) {
250 if (sb.length() > 0) {
251 sb.append("&");
252 }
253 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
254 sb.append("time").append("=").append(df.format(closedAfter));
255 sb.append(",").append(df.format(createdBefore));
256 } else if (closedAfter != null) {
257 if (sb.length() > 0) {
258 sb.append("&");
259 }
260 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
261 sb.append("time").append("=").append(df.format(closedAfter));
262 }
263
264 if (open != null) {
265 if (sb.length() > 0) {
266 sb.append("&");
267 }
268 sb.append("open=").append(Boolean.toString(open));
269 } else if (closed != null) {
270 if (sb.length() > 0) {
271 sb.append("&");
272 }
273 sb.append("closed=").append(Boolean.toString(closed));
274 }
275 return sb.toString();
276 }
277
278 @Override
279 public String toString() {
280 return getQueryString();
281 }
282
283 public static class ChangesetQueryUrlException extends Exception {
284
285 public ChangesetQueryUrlException() {
286 super();
287 }
288
289 public ChangesetQueryUrlException(String arg0, Throwable arg1) {
290 super(arg0, arg1);
291 }
292
293 public ChangesetQueryUrlException(String arg0) {
294 super(arg0);
295 }
296
297 public ChangesetQueryUrlException(Throwable arg0) {
298 super(arg0);
299 }
300 }
301
302 public static class ChangesetQueryUrlParser {
303 protected int parseUid(String value) throws ChangesetQueryUrlException {
304 if (value == null || value.trim().equals(""))
305 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid",value));
306 int id;
307 try {
308 id = Integer.parseInt(value);
309 if (id <= 0)
310 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid",value));
311 } catch(NumberFormatException e) {
312 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "uid",value));
313 }
314 return id;
315 }
316
317 protected boolean parseOpen(String value) throws ChangesetQueryUrlException {
318 if (value == null || value.trim().equals(""))
319 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "open",value));
320 if (value.equals("true"))
321 return true;
322 else if (value.equals("false"))
323 return false;
324 else
325 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "open",value));
326 }
327
328 protected boolean parseBoolean(String value, String parameter) throws ChangesetQueryUrlException {
329 if (value == null || value.trim().equals(""))
330 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter,value));
331 if (value.equals("true"))
332 return true;
333 else if (value.equals("false"))
334 return false;
335 else
336 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter,value));
337 }
338
339 protected Date parseDate(String value, String parameter) throws ChangesetQueryUrlException {
340 if (value == null || value.trim().equals(""))
341 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter,value));
342 if (value.endsWith("Z")) {
343 // OSM API generates date strings we time zone abbreviation "Z" which Java SimpleDateFormat
344 // doesn't understand. Convert into GMT time zone before parsing.
345 //
346 value = value.substring(0,value.length() - 1) + "GMT+00:00";
347 }
348 DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
349 try {
350 return formatter.parse(value);
351 } catch(ParseException e) {
352 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", parameter,value));
353 }
354 }
355
356 protected Date[] parseTime(String value) throws ChangesetQueryUrlException {
357 String[] dates = value.split(",");
358 if (dates == null || dates.length == 0 || dates.length > 2)
359 throw new ChangesetQueryUrlException(tr("Unexpected value for ''{0}'' in changeset query url, got {1}", "time", value));
360 if (dates.length == 1)
361 return new Date[]{parseDate(dates[0], "time")};
362 else if (dates.length == 2)
363 return new Date[]{parseDate(dates[0], "time"),parseDate(dates[1], "time")};
364 return null;
365 }
366
367 protected ChangesetQuery crateFromMap(Map<String,String> queryParams) throws ChangesetQueryUrlException {
368 ChangesetQuery csQuery = new ChangesetQuery();
369
370 for (String k: queryParams.keySet()) {
371 if (k.equals("uid")) {
372 if (queryParams.containsKey("display_name"))
373 throw new ChangesetQueryUrlException(tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
374 csQuery.forUser(parseUid(queryParams.get("uid")));
375 } else if (k.equals("display_name")) {
376 if (queryParams.containsKey("uid"))
377 throw new ChangesetQueryUrlException(tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
378 csQuery.forUser(queryParams.get("display_name"));
379 } else if (k.equals("open")) {
380 boolean b = parseBoolean(queryParams.get(k), "open");
381 csQuery.beingOpen(b);
382 } else if (k.equals("closed")) {
383 boolean b = parseBoolean(queryParams.get(k), "closed");
384 csQuery.beingClosed(b);
385 } else if (k.equals("time")) {
386 Date[] dates = parseTime(queryParams.get(k));
387 switch(dates.length) {
388 case 1:
389 csQuery.closedAfter(dates[0]);
390 break;
391 case 2:
392 csQuery.closedAfterAndCreatedBefore(dates[0], dates[1]);
393 break;
394 }
395 } else if (k.equals("bbox")) {
396 try {
397 csQuery.inBbox(new Bounds(queryParams.get(k), ","));
398 } catch(IllegalArgumentException e) {
399 throw new ChangesetQueryUrlException(e);
400 }
401 } else
402 throw new ChangesetQueryUrlException(tr("Unsupported parameter ''{0}'' in changeset query string",k ));
403 }
404 return csQuery;
405 }
406
407 protected Map<String,String> createMapFromQueryString(String query) {
408 Map<String,String> queryParams = new HashMap<String, String>();
409 String[] keyValuePairs = query.split("&");
410 for (String keyValuePair: keyValuePairs) {
411 String[] kv = keyValuePair.split("=");
412 queryParams.put(kv[0], kv[1]);
413 }
414 return queryParams;
415 }
416
417 /**
418 * Parses the changeset query given as URL query parameters and replies a
419 * {@link ChangesetQuery}
420 *
421 * <code>query</code> is the query part of a API url for querying changesets,
422 * see <a href="http://wiki.openstreetmap.org/wiki/API_v0.6#Query:_GET_.2Fapi.2F0.6.2Fchangesets">OSM API</a>.
423 *
424 * Example for an query string:<br>
425 * <pre>
426 * uid=1234&open=true
427 * </pre>
428 *
429 * @param query the query string. If null, an empty query (identical to a query for all changesets) is
430 * assumed
431 * @return the changeset query
432 * @throws ChangesetQueryUrlException if the query string doesn't represent a legal query for changesets
433 */
434 public ChangesetQuery parse(String query) throws ChangesetQueryUrlException{
435 if (query == null)
436 return new ChangesetQuery();
437 query = query.trim();
438 if (query.equals(""))
439 return new ChangesetQuery();
440 Map<String,String> queryParams = createMapFromQueryString(query);
441 return crateFromMap(queryParams);
442 }
443 }
444}
Note: See TracBrowser for help on using the repository browser.