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

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

fix some Sonar issues (JLS order)

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