Ticket #10434: notes_parsing.patch

File notes_parsing.patch, 15.9 KB (added by ToeBee, 11 years ago)
  • src/org/openstreetmap/josm/data/notes/Note.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.notes;
     3
     4import java.util.ArrayList;
     5import java.util.Date;
     6import java.util.List;
     7
     8import org.openstreetmap.josm.data.coor.LatLon;
     9
     10/**
     11 * A map note. It always has at least one comment since a comment is required
     12 * to create a note on osm.org
     13 */
     14public class Note {
     15
     16    public enum State { open, closed }
     17
     18    private long id;
     19    private LatLon latLon;
     20    private Date createdAt;
     21    private Date closedAt;
     22    private State state;
     23    private List<NoteComment> comments = new ArrayList<NoteComment>();
     24
     25    /**
     26     * Create a note with a given location
     27     * @param latLon Geographic location of this note
     28     */
     29    public Note(LatLon latLon) {
     30        this.latLon = latLon;
     31    }
     32
     33    /** @return The unique OSM ID of this note */
     34    public long getId() {
     35        return id;
     36    }
     37
     38    public void setId(long id) {
     39        this.id = id;
     40    }
     41
     42    /** @return The geographic location of the note */
     43    public LatLon getLatLon() {
     44        return latLon;
     45    }
     46
     47    /** @return Date that this note was submitted */
     48    public Date getCreatedAt() {
     49        return createdAt;
     50    }
     51
     52    public void setCreatedAt(Date createdAt) {
     53        this.createdAt = createdAt;
     54    }
     55
     56    /** @return Date that this note was closed. Null if it is still open. */
     57    public Date getClosedAt() {
     58        return closedAt;
     59    }
     60
     61    public void setClosedAt(Date closedAt) {
     62        this.closedAt = closedAt;
     63    }
     64
     65    /** @return The open or closed state of this note */
     66    public State getState() {
     67        return state;
     68    }
     69
     70    public void setState(State state) {
     71        this.state = state;
     72    }
     73
     74    /** @return An ordered list of comments associated with this note */
     75    public List<NoteComment> getComments() {
     76        return comments;
     77    }
     78
     79    public void addComment(NoteComment comment) {
     80        this.comments.add(comment);
     81    }
     82
     83    /**
     84     * Returns the comment that was submitted by the user when creating the note
     85     * @return First comment object
     86     */
     87    public NoteComment getFirstComment() {
     88        return this.comments.get(0);
     89    }
     90
     91    /**
     92     * Copies values from a new note into an existing one. Used after a note
     93     * has been updated on the server and the local copy needs refreshing.
     94     * @param note New values to copy
     95     */
     96    public void updateWith(Note note) {
     97        this.comments = note.comments;
     98        this.createdAt = note.createdAt;
     99        this.id = note.id;
     100        this.state = note.state;
     101        this.latLon = note.latLon;
     102    }
     103
     104    @Override
     105    public int hashCode() {
     106        final int prime = 31;
     107        int result = 1;
     108        result = prime * result + (int) (id ^ (id >>> 32));
     109        return result;
     110    }
     111
     112    /** Compares notes by OSM ID */
     113    @Override
     114    public boolean equals(Object obj) {
     115        if (this == obj)
     116            return true;
     117        if (obj == null)
     118            return false;
     119        if (getClass() != obj.getClass())
     120            return false;
     121        Note other = (Note) obj;
     122        if (id != other.id)
     123            return false;
     124        return true;
     125    }
     126}
  • src/org/openstreetmap/josm/data/notes/NoteComment.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.notes;
     3
     4import java.util.Date;
     5
     6import org.openstreetmap.josm.data.osm.User;
     7
     8/**
     9 * Represents a comment made on a note. All notes have at least on comment
     10 * which is the comment the note was opened with. Comments are immutable.
     11 */
     12public class NoteComment {
     13
     14    private String text;
     15    private User user;
     16    private Date commentTimestamp;
     17    private Action action;
     18
     19    //not currently used. I'm planning on using this to keep track of new actions that need to be uploaded
     20    private Boolean isNew;
     21
     22    /**
     23     * Every comment has an associated action. Some comments are just comments
     24     * while others indicate the note being opened, closed or reopened
     25     */
     26    public enum Action {opened, closed, reopened, commented}
     27
     28    /**
     29     * @param createDate The time at which this comment was added
     30     * @param user JOSM User object of the user who created the comment
     31     * @param commentText The text left by the user. Is sometimes blank
     32     * @param action The action associated with this comment
     33     * @param isNew Whether or not this comment is new and needs to be uploaded
     34     */
     35    public NoteComment(Date createDate, User user, String commentText, Action action, Boolean isNew) {
     36        this.text = commentText;
     37        this.user = user;
     38        this.commentTimestamp = createDate;
     39        this.action = action;
     40        this.isNew = isNew;
     41    }
     42
     43    /** @return Plain text of user's comment */
     44    public String getText() {
     45        return text;
     46    }
     47
     48    /** @return JOSM's User object for the user who made this comment */
     49    public User getUser() {
     50        return user;
     51    }
     52
     53    /** @return The time at which this comment was created */
     54    public Date getCommentTimestamp() {
     55        return commentTimestamp;
     56    }
     57
     58    /** @return the action associated with this note */
     59    public Action getNoteAction() {
     60        return action;
     61    }
     62
     63    public void setIsNew(Boolean isNew) {
     64        this.isNew = isNew;
     65    }
     66
     67    /** @return true if this is a new comment/action and needs to be uploaded to the API */
     68    public Boolean getIsNew() {
     69        return isNew;
     70    }
     71}
  • src/org/openstreetmap/josm/io/NoteReader.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io;
     3
     4import java.io.IOException;
     5import java.io.InputStream;
     6import java.text.ParseException;
     7import java.text.SimpleDateFormat;
     8import java.util.ArrayList;
     9import java.util.Date;
     10import java.util.List;
     11import java.util.Locale;
     12
     13import javax.xml.parsers.ParserConfigurationException;
     14import javax.xml.parsers.SAXParserFactory;
     15
     16import org.openstreetmap.josm.Main;
     17import org.openstreetmap.josm.data.coor.LatLon;
     18import org.openstreetmap.josm.data.notes.Note;
     19import org.openstreetmap.josm.data.notes.NoteComment;
     20import org.openstreetmap.josm.data.notes.NoteComment.Action;
     21import org.openstreetmap.josm.data.osm.User;
     22import org.xml.sax.Attributes;
     23import org.xml.sax.InputSource;
     24import org.xml.sax.SAXException;
     25import org.xml.sax.helpers.DefaultHandler;
     26
     27/**
     28 * Class to read Note objects from their XML representation
     29 */
     30public class NoteReader {
     31
     32    private InputSource inputSource;
     33    private List<Note> parsedNotes;
     34    private NoteParseMode parseMode;
     35
     36    /**
     37     * Notes can be represented in two XML formats. One is returned by the API
     38     * while the other is used to generate the notes dump file. The parser
     39     * needs to know which one it is handling.
     40     */
     41    public enum NoteParseMode {API, DUMP}
     42
     43    /**
     44     * Parser for the notes dump file format.
     45     * It is completely different from the API XML format.
     46     */
     47    private class DumpParser extends DefaultHandler {
     48        private StringBuffer buffer = new StringBuffer();
     49        private final SimpleDateFormat ISO8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX", Locale.ENGLISH);
     50
     51        private List<Note> notes = new ArrayList<Note>(100000);
     52        private Note thisNote;
     53
     54        private Date commentCreateDate;
     55        private String commentUsername;
     56        private long commentUid;
     57        private Action noteAction;
     58        private Boolean commentIsNew;
     59
     60        @Override
     61        public void characters(char[] ch, int start, int length) throws SAXException {
     62            buffer.append(ch, start, length);
     63        }
     64
     65        @Override
     66        public void endElement(String uri, String localName, String qName) throws SAXException {
     67            switch (qName) {
     68                case "note":
     69                    notes.add(thisNote);
     70                    break;
     71                case "comment":
     72                    User commentUser = User.createOsmUser(commentUid, commentUsername);
     73                    thisNote.addComment(new NoteComment(commentCreateDate, commentUser, buffer.toString(), noteAction, commentIsNew));
     74                    commentUid = 0;
     75                    commentUsername = null;
     76                    commentCreateDate = null;
     77                    commentIsNew = null;
     78                    break;
     79            }
     80        }
     81
     82        @Override
     83        public void startElement(String uri, String localName, String qName, Attributes attrs) throws SAXException {
     84            buffer.setLength(0);
     85            switch(qName) {
     86            case "note":
     87                double lat = Double.parseDouble(attrs.getValue("lat"));
     88                double lon = Double.parseDouble(attrs.getValue("lon"));
     89                LatLon noteLatLon = new LatLon(lat, lon);
     90                thisNote = new Note(noteLatLon);
     91                thisNote.setId(Long.parseLong(attrs.getValue("id")));
     92                String closedTimeStr = attrs.getValue("closed_at");
     93                if(closedTimeStr == null) { //no closed_at means the note is still open
     94                    thisNote.setState(Note.State.open);
     95                } else {
     96                    thisNote.setState(Note.State.closed);
     97                    thisNote.setClosedAt(parseDate(ISO8601_FORMAT, closedTimeStr));
     98                }
     99                thisNote.setCreatedAt(parseDate(ISO8601_FORMAT, attrs.getValue("created_at")));
     100                break;
     101            case "comment":
     102                String uidStr = attrs.getValue("uid");
     103                if(uidStr == null) {
     104                    commentUid = 0;
     105                } else {
     106                    commentUid = Long.parseLong(uidStr);
     107                }
     108                commentUsername = attrs.getValue("user");
     109                noteAction = Action.valueOf(attrs.getValue("action"));
     110                commentCreateDate = parseDate(ISO8601_FORMAT, attrs.getValue("timestamp"));
     111                String isNew = attrs.getValue("is_new");
     112                if(isNew == null) {
     113                    commentIsNew = false;
     114                } else {
     115                    commentIsNew = Boolean.valueOf(isNew);
     116                }
     117                break;
     118            }
     119        }
     120
     121        @Override
     122        public void endDocument() throws SAXException  {
     123            Main.info("parsed notes: " + notes.size());
     124            parsedNotes = notes;
     125        }
     126    }
     127
     128    private class ApiParser extends DefaultHandler {
     129
     130        private StringBuffer accumulator = new StringBuffer();
     131        private final SimpleDateFormat NOTE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.ENGLISH);
     132
     133        private List<Note> notes = new ArrayList<Note>();
     134        private Note thisNote;
     135
     136        private Date commentCreateDate;
     137        private String commentUsername;
     138        private long commentUid;
     139        private String commentText;
     140        private Action commentAction;
     141
     142        @Override
     143        public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
     144            accumulator.setLength(0);
     145            if ("note".equals(qName)) {
     146                double lat = Double.parseDouble(atts.getValue("lat"));
     147                double lon = Double.parseDouble(atts.getValue("lon"));
     148                LatLon noteLatLon = new LatLon(lat, lon);
     149                thisNote = new Note(noteLatLon);
     150            }
     151        }
     152
     153        @Override
     154        public void characters(char[] ch, int start, int length) {
     155            accumulator.append(ch, start, length);
     156        }
     157
     158        @Override
     159        public void endElement(String namespaceURI, String localName, String qName) {
     160            switch (qName) {
     161            case "id":
     162                thisNote.setId(Long.parseLong(accumulator.toString()));
     163                break;
     164            case "status":
     165                thisNote.setState(Note.State.valueOf(accumulator.toString()));
     166                break;
     167            case "date_created":
     168                thisNote.setCreatedAt(parseDate(NOTE_DATE_FORMAT, accumulator.toString()));
     169                break;
     170            case "note":
     171                notes.add(thisNote);
     172                break;
     173            case "date":
     174                commentCreateDate = parseDate(NOTE_DATE_FORMAT, accumulator.toString());
     175                break;
     176            case "user":
     177                commentUsername = accumulator.toString();
     178                break;
     179            case "uid":
     180                commentUid = Long.parseLong(accumulator.toString());
     181                break;
     182            case "text":
     183                commentText = accumulator.toString();
     184                break;
     185            case "comment":
     186                User commentUser = User.createOsmUser(commentUid, commentUsername);
     187                thisNote.addComment(new NoteComment(commentCreateDate, commentUser, commentText, commentAction, false));
     188                commentUid = 0;
     189                commentUsername = null;
     190                commentCreateDate = null;
     191                commentText = null;
     192                break;
     193            case "action":
     194                commentAction = Action.valueOf(accumulator.toString());
     195                break;
     196            }
     197        }
     198
     199        @Override
     200        public void endDocument() throws SAXException  {
     201            Main.info("parsed notes: " + notes.size());
     202            parsedNotes = notes;
     203        }
     204    }
     205
     206    /**
     207     * Convenience method to handle the date parsing try/catch. Will return null if
     208     * there is a parsing exception. This means whatever generated this XML is in error
     209     * and there isn't anything we can do about it.
     210     * @param dateStr - String to parse
     211     * @return Parsed date, null if parsing fails
     212     */
     213    private Date parseDate(SimpleDateFormat sdf, String dateStr) {
     214        try {
     215            return sdf.parse(dateStr);
     216        } catch(ParseException e) {
     217            Main.error("error parsing date in note parser");
     218            return null;
     219        }
     220    }
     221
     222    /**
     223     * Initializes the reader with a given InputStream
     224     * @param source - InputStream containing Notes XML
     225     * @param parseMode - Indicate if we are parsing API or dump file style XML
     226     * @throws IOException
     227     */
     228    public NoteReader(InputStream source, NoteParseMode parseMode) throws IOException {
     229        this.inputSource = new InputSource(source);
     230        this.parseMode = parseMode;
     231    }
     232
     233    /**
     234     * Parses the InputStream given to the constructor and returns
     235     * the resulting Note objects
     236     * @return List of Notes parsed from the input data
     237     * @throws SAXException
     238     * @throws IOException
     239     */
     240    public List<Note> parse() throws SAXException, IOException {
     241        DefaultHandler parser;
     242        if(parseMode == NoteParseMode.DUMP) {
     243            parser = new DumpParser();
     244        } else {
     245            parser = new ApiParser();
     246        }
     247        try {
     248            SAXParserFactory factory = SAXParserFactory.newInstance();
     249            factory.setNamespaceAware(true);
     250            factory.newSAXParser().parse(inputSource, parser);
     251        } catch (ParserConfigurationException e) {
     252            Main.error(e); // broken SAXException chaining
     253            throw new SAXException(e);
     254        }
     255        return parsedNotes;
     256    }
     257}