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

Last change on this file since 6388 was 6258, checked in by Don-vip, 11 years ago

Sonar/Findbugs - Performance - Inefficient use of keySet iterator instead of entrySet iterator

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