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

Revision 4902, 18.1 KB checked in by Don-vip, 4 months ago (diff)

fix #7355 - Query for a user name does not return any results

  • Property svn:eol-style set to native
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 = null;
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.