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

Last change on this file since 6440 was 6440, checked in by simon04, 10 years ago

Load and display changeset comment in history dialog

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