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

Last change on this file since 4067 was 3083, checked in by bastiK, 14 years ago

added svn:eol-style=native to source files

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