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

Last change on this file since 6890 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
RevLine 
[2512]1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
[2688]4import static org.openstreetmap.josm.tools.I18n.tr;
5
[5366]6import java.io.UnsupportedEncodingException;
7import java.net.URLEncoder;
[2688]8import java.text.DateFormat;
[2852]9import java.text.MessageFormat;
[2688]10import java.text.ParseException;
11import java.text.SimpleDateFormat;
[6440]12import java.util.Arrays;
13import java.util.Collection;
14import java.util.Collections;
[2512]15import java.util.Date;
[2688]16import java.util.HashMap;
[6440]17import java.util.HashSet;
[2688]18import java.util.Map;
[6258]19import java.util.Map.Entry;
[2512]20
[6643]21import org.openstreetmap.josm.Main;
[2688]22import org.openstreetmap.josm.data.Bounds;
[2512]23import org.openstreetmap.josm.data.coor.LatLon;
[2688]24import org.openstreetmap.josm.tools.CheckParameterUtil;
[6440]25import org.openstreetmap.josm.tools.Utils;
[2512]26
[2688]27public class ChangesetQuery {
[2512]28
[2688]29 /**
30 * Replies a changeset query object from the query part of a OSM API URL for querying
31 * changesets.
[2711]32 *
[2688]33 * @param query the query part
34 * @return the query object
35 * @throws ChangesetQueryUrlException thrown if query doesn't consist of valid query parameters
[2711]36 *
[2688]37 */
[6889]38 public static ChangesetQuery buildFromUrlQuery(String query) throws ChangesetQueryUrlException{
[2688]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
[2512]49 private Date closedAfter = null;
50 private Date createdBefore = null;
[2688]51 /** indicates whether only open changesets are queried. null, if no restrictions regarding open changesets apply */
[2512]52 private Boolean open = null;
[2688]53 /** indicates whether only closed changesets are queried. null, if no restrictions regarding open changesets apply */
[2512]54 private Boolean closed = null;
[6440]55 /** a collection of changeset ids to query for */
56 private Collection<Long> changesetIds = null;
[2512]57
[6830]58 /**
59 * Constructs a new {@code ChangesetQuery}.
60 */
61 public ChangesetQuery() {
[2512]62
[6830]63 }
64
[2688]65 /**
66 * Restricts the query to changesets owned by the user with id <code>uid</code>.
[2711]67 *
[6830]68 * @param uid the uid of the user. &gt; 0 expected.
[2688]69 * @return the query object with the applied restriction
[6830]70 * @throws IllegalArgumentException thrown if uid &lt;= 0
[2688]71 * @see #forUser(String)
72 */
73 public ChangesetQuery forUser(int uid) throws IllegalArgumentException{
[2512]74 if (uid <= 0)
[2852]75 throw new IllegalArgumentException(MessageFormat.format("Parameter ''{0}'' > 0 expected. Got ''{1}''.", "uid", uid));
[2688]76 this.uid = uid;
77 this.userName = null;
[2512]78 return this;
79 }
80
[2688]81 /**
82 * Restricts the query to changesets owned by the user with user name <code>username</code>.
[2711]83 *
[2688]84 * Caveat: for historical reasons the username might not be unique! It is recommended to use
[5266]85 * {@link #forUser(int)} to restrict the query to a specific user.
[2711]86 *
[2688]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;
[4902]95 this.uid = null;
[2688]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.
[2711]102 *
[2688]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
[5266]112 * restricted to a user name, i.e. if {@link #isRestrictedToPartiallyIdentifiedUser()} is false.
[2711]113 *
[2688]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.
[2711]122 *
[2688]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.
[2711]131 *
[2688]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.
[2711]136 *
[2688]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
[2512]151 return inBbox(new LatLon(minLon, minLat), new LatLon(maxLon, maxLat));
152 }
153
[2688]154 /**
155 * Replies a query which is restricted to a bounding box.
[2711]156 *
[2688]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.
[2711]159 *
[2688]160 * @return the restricted changeset query
161 * @throws IllegalArgumentException thrown if min is null
162 * @throws IllegalArgumentException thrown if max is null
163 */
[2512]164 public ChangesetQuery inBbox(LatLon min, LatLon max) {
[2688]165 CheckParameterUtil.ensureParameterNotNull(min, "min");
166 CheckParameterUtil.ensureParameterNotNull(max, "max");
167 this.bounds = new Bounds(min,max);
[2512]168 return this;
169 }
170
[2688]171 /**
172 * Replies a query which is restricted to a bounding box given by <code>bbox</code>.
[2711]173 *
[2688]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.
[2711]187 *
[2688]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");
[2512]194 this.closedAfter = d;
195 return this;
196 }
197
[2688]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.
[2711]202 *
[2688]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");
[2512]212 this.closedAfter = closedAfter;
213 this.createdBefore = createdBefore;
214 return this;
215 }
216
[2688]217 /**
218 * Restricts the result to changesets which are or aren't open, depending on the value of
219 * <code>isOpen</code>
[2711]220 *
[2688]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;
[2512]226 return this;
227 }
228
[2688]229 /**
230 * Restricts the result to changesets which are or aren't closed, depending on the value of
231 * <code>isClosed</code>
[2711]232 *
[2688]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;
[2512]238 return this;
239 }
240
[2688]241 /**
[6440]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 /**
[2688]255 * Replies the query string to be used in a query URL for the OSM API.
[2711]256 *
[2688]257 * @return the query string
258 */
[2512]259 public String getQueryString() {
[6822]260 StringBuilder sb = new StringBuilder();
[2688]261 if (uid != null) {
262 sb.append("user").append("=").append(uid);
263 } else if (userName != null) {
[5366]264 try {
265 sb.append("display_name").append("=").append(URLEncoder.encode(userName, "UTF-8"));
266 } catch (UnsupportedEncodingException e) {
[6643]267 Main.error(e);
[5366]268 }
[2512]269 }
[2688]270 if (bounds != null) {
[2512]271 if (sb.length() > 0) {
272 sb.append("&");
273 }
[2688]274 sb.append("bbox=").append(bounds.encodeAsString(","));
[2512]275 }
276 if (closedAfter != null && createdBefore != null) {
277 if (sb.length() > 0) {
278 sb.append("&");
279 }
[2688]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));
[2512]283 } else if (closedAfter != null) {
284 if (sb.length() > 0) {
285 sb.append("&");
286 }
[2688]287 SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
288 sb.append("time").append("=").append(df.format(closedAfter));
[2512]289 }
290
291 if (open != null) {
292 if (sb.length() > 0) {
293 sb.append("&");
294 }
[2688]295 sb.append("open=").append(Boolean.toString(open));
[2512]296 } else if (closed != null) {
297 if (sb.length() > 0) {
298 sb.append("&");
299 }
[2688]300 sb.append("closed=").append(Boolean.toString(closed));
[6440]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));
[2512]307 }
308 return sb.toString();
309 }
[2688]310
[5366]311 @Override
312 public String toString() {
313 return getQueryString();
314 }
315
[2688]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 {
[6087]337 if (value == null || value.trim().isEmpty())
[2688]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 {
[6087]351 if (value == null || value.trim().isEmpty())
[2688]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 {
[6087]362 if (value == null || value.trim().isEmpty())
[2688]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 {
[6087]373 if (value == null || value.trim().isEmpty())
[2688]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 }
[2789]381 DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
[2688]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
[6440]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 {
[2688]412 ChangesetQuery csQuery = new ChangesetQuery();
413
[6258]414 for (Entry<String, String> entry: queryParams.entrySet()) {
415 String k = entry.getKey();
[2688]416 if (k.equals("uid")) {
417 if (queryParams.containsKey("display_name"))
[2852]418 throw new ChangesetQueryUrlException(tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
[2688]419 csQuery.forUser(parseUid(queryParams.get("uid")));
420 } else if (k.equals("display_name")) {
421 if (queryParams.containsKey("uid"))
[2852]422 throw new ChangesetQueryUrlException(tr("Cannot create a changeset query including both the query parameters ''uid'' and ''display_name''"));
[2688]423 csQuery.forUser(queryParams.get("display_name"));
424 } else if (k.equals("open")) {
[6258]425 boolean b = parseBoolean(entry.getValue(), "open");
[2688]426 csQuery.beingOpen(b);
427 } else if (k.equals("closed")) {
[6258]428 boolean b = parseBoolean(entry.getValue(), "closed");
[2688]429 csQuery.beingClosed(b);
430 } else if (k.equals("time")) {
[6258]431 Date[] dates = parseTime(entry.getValue());
[2688]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 {
[6258]442 csQuery.inBbox(new Bounds(entry.getValue(), ","));
[2688]443 } catch(IllegalArgumentException e) {
444 throw new ChangesetQueryUrlException(e);
445 }
[6440]446 } else if (k.equals("changesets")) {
447 try {
448 csQuery.forChangesetIds(parseLongs(entry.getValue()));
449 } catch (NumberFormatException e) {
450 throw new ChangesetQueryUrlException(e);
451 }
[2688]452 } else
[6258]453 throw new ChangesetQueryUrlException(tr("Unsupported parameter ''{0}'' in changeset query string", k));
[2688]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("=");
[6440]463 queryParams.put(kv[0], kv.length > 1 ? kv[1] : "");
[2688]464 }
465 return queryParams;
466 }
467
468 /**
[6830]469 * Parses the changeset query given as URL query parameters and replies a {@link ChangesetQuery}.
[2711]470 *
[2688]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>.
[2711]473 *
[2688]474 * Example for an query string:<br>
475 * <pre>
[6830]476 * uid=1234&amp;open=true
[2688]477 * </pre>
[2711]478 *
[2688]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();
[6087]488 if (query.isEmpty())
[2688]489 return new ChangesetQuery();
490 Map<String,String> queryParams = createMapFromQueryString(query);
[6440]491 return createFromMap(queryParams);
[2688]492 }
493 }
[2512]494}
Note: See TracBrowser for help on using the repository browser.