001    // License: GPL. Copyright 2007 by Immanuel Scholz and others
002    package org.openstreetmap.josm.io;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.io.IOException;
007    import java.io.InputStream;
008    import java.io.InputStreamReader;
009    import java.util.Date;
010    
011    import javax.xml.parsers.ParserConfigurationException;
012    import javax.xml.parsers.SAXParserFactory;
013    
014    import org.openstreetmap.josm.data.coor.LatLon;
015    import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
016    import org.openstreetmap.josm.data.osm.RelationMemberData;
017    import org.openstreetmap.josm.data.osm.User;
018    import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
019    import org.openstreetmap.josm.data.osm.history.HistoryNode;
020    import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
021    import org.openstreetmap.josm.data.osm.history.HistoryRelation;
022    import org.openstreetmap.josm.data.osm.history.HistoryWay;
023    import org.openstreetmap.josm.gui.progress.ProgressMonitor;
024    import org.openstreetmap.josm.tools.DateUtils;
025    import org.xml.sax.Attributes;
026    import org.xml.sax.InputSource;
027    import org.xml.sax.Locator;
028    import org.xml.sax.SAXException;
029    import org.xml.sax.helpers.DefaultHandler;
030    
031    /**
032     * Parser for OSM history data.
033     *
034     * It is slightly different from {@link OsmReader} because we don't build an internal graph of
035     * {@link OsmPrimitive}s. We use objects derived from {@link HistoryOsmPrimitive} instead and we
036     * keep the data in a dedicated {@link HistoryDataSet}.
037     *
038     */
039    public class OsmHistoryReader {
040    
041        private final InputStream in;
042        private final HistoryDataSet data;
043    
044        // FIXME: this class has many similarities with OsmChangesetContentParser.Parser and should be merged 
045        private class Parser extends DefaultHandler {
046    
047            /** the current primitive to be read */
048            private HistoryOsmPrimitive current;
049            private Locator locator;
050    
051            @Override
052            public void setDocumentLocator(Locator locator) {
053                this.locator = locator;
054            }
055    
056            protected String getCurrentPosition() {
057                if (locator == null)
058                    return "";
059                return "(" + locator.getLineNumber() + "," + locator.getColumnNumber() + ")";
060            }
061    
062            protected void throwException(String message) throws SAXException {
063                throw new SAXException(
064                        getCurrentPosition()
065                        +   message
066                );
067            }
068    
069            protected long getMandatoryAttributeLong(Attributes attr, String name) throws SAXException{
070                String v = attr.getValue(name);
071                if (v == null) {
072                    throwException(tr("Missing mandatory attribute ''{0}''.", name));
073                }
074                Long l = 0l;
075                try {
076                    l = Long.parseLong(v);
077                } catch(NumberFormatException e) {
078                    throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v));
079                }
080                if (l < 0) {
081                    throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v));
082                }
083                return l;
084            }
085    
086            protected Long getAttributeLong(Attributes attr, String name) throws SAXException {
087                String v = attr.getValue(name);
088                if (v == null)
089                    return null;
090                Long l = null;
091                try {
092                    l = Long.parseLong(v);
093                } catch(NumberFormatException e) {
094                    throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long. Got ''{1}''.", name, v));
095                }
096                if (l < 0) {
097                    throwException(tr("Illegal value for mandatory attribute ''{0}'' of type long (>=0). Got ''{1}''.", name, v));
098                }
099                return l;
100            }
101    
102            protected Double getAttributeDouble(Attributes attr, String name) throws SAXException{
103                String v = attr.getValue(name);
104                if (v == null) {
105                    return null;
106                }
107                double d = 0.0;
108                try {
109                    d = Double.parseDouble(v);
110                } catch(NumberFormatException e) {
111                    throwException(tr("Illegal value for attribute ''{0}'' of type double. Got ''{1}''.", name, v));
112                }
113                return d;
114            }
115    
116            protected String getMandatoryAttributeString(Attributes attr, String name) throws SAXException{
117                String v = attr.getValue(name);
118                if (v == null) {
119                    throwException(tr("Missing mandatory attribute ''{0}''.", name));
120                }
121                return v;
122            }
123    
124            protected boolean getMandatoryAttributeBoolean(Attributes attr, String name) throws SAXException{
125                String v = attr.getValue(name);
126                if (v == null) {
127                    throwException(tr("Missing mandatory attribute ''{0}''.", name));
128                }
129                if ("true".equals(v)) return true;
130                if ("false".equals(v)) return false;
131                throwException(tr("Illegal value for mandatory attribute ''{0}'' of type boolean. Got ''{1}''.", name, v));
132                // not reached
133                return false;
134            }
135    
136            protected  HistoryOsmPrimitive createPrimitive(Attributes atts, OsmPrimitiveType type) throws SAXException {
137                long id = getMandatoryAttributeLong(atts,"id");
138                long version = getMandatoryAttributeLong(atts,"version");
139                long changesetId = getMandatoryAttributeLong(atts,"changeset");
140                boolean visible= getMandatoryAttributeBoolean(atts, "visible");
141                Long uid = getAttributeLong(atts, "uid");
142                String userStr = atts.getValue("user");
143                User user;
144                if (userStr != null) {
145                    if (uid != null) {
146                        user = User.createOsmUser(uid, userStr);
147                    } else {
148                        user = User.createLocalUser(userStr);
149                    }
150                } else {
151                    user = User.getAnonymous();
152                }
153                String v = getMandatoryAttributeString(atts, "timestamp");
154                Date timestamp = DateUtils.fromString(v);
155                HistoryOsmPrimitive primitive = null;
156                if (type.equals(OsmPrimitiveType.NODE)) {
157                    Double lat = getAttributeDouble(atts, "lat");
158                    Double lon = getAttributeDouble(atts, "lon");
159                    LatLon coord = (lat != null && lon != null) ? new LatLon(lat,lon) : null;
160                    primitive = new HistoryNode(
161                            id,version,visible,user,changesetId,timestamp,coord
162                    );
163    
164                } else if (type.equals(OsmPrimitiveType.WAY)) {
165                    primitive = new HistoryWay(
166                            id,version,visible,user,changesetId,timestamp
167                    );
168                }if (type.equals(OsmPrimitiveType.RELATION)) {
169                    primitive = new HistoryRelation(
170                            id,version,visible,user,changesetId,timestamp
171                    );
172                }
173                return primitive;
174            }
175    
176            protected void startNode(Attributes atts) throws SAXException {
177                current= createPrimitive(atts, OsmPrimitiveType.NODE);
178            }
179    
180            protected void startWay(Attributes atts) throws SAXException {
181                current= createPrimitive(atts, OsmPrimitiveType.WAY);
182            }
183            protected void startRelation(Attributes atts) throws SAXException {
184                current= createPrimitive(atts, OsmPrimitiveType.RELATION);
185            }
186    
187            protected void handleTag(Attributes atts) throws SAXException {
188                String key= getMandatoryAttributeString(atts, "k");
189                String value= getMandatoryAttributeString(atts, "v");
190                current.put(key,value);
191            }
192    
193            protected void handleNodeReference(Attributes atts) throws SAXException {
194                long ref = getMandatoryAttributeLong(atts, "ref");
195                ((HistoryWay)current).addNode(ref);
196            }
197    
198            protected void handleMember(Attributes atts) throws SAXException {
199                long ref = getMandatoryAttributeLong(atts, "ref");
200                String v = getMandatoryAttributeString(atts, "type");
201                OsmPrimitiveType type = null;
202                try {
203                    type = OsmPrimitiveType.fromApiTypeName(v);
204                } catch(IllegalArgumentException e) {
205                    throwException(tr("Illegal value for mandatory attribute ''{0}'' of type OsmPrimitiveType. Got ''{1}''.", "type", v));
206                }
207                String role = getMandatoryAttributeString(atts, "role");
208                RelationMemberData member = new RelationMemberData(role, type,ref);
209                ((HistoryRelation)current).addMember(member);
210            }
211    
212            @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
213                if (qName.equals("node")) {
214                    startNode(atts);
215                } else if (qName.equals("way")) {
216                    startWay(atts);
217                } else if (qName.equals("relation")) {
218                    startRelation(atts);
219                } else if (qName.equals("tag")) {
220                    handleTag(atts);
221                } else if (qName.equals("nd")) {
222                    handleNodeReference(atts);
223                } else if (qName.equals("member")) {
224                    handleMember(atts);
225                }
226            }
227    
228            @Override
229            public void endElement(String uri, String localName, String qName) throws SAXException {
230                if (qName.equals("node")
231                        || qName.equals("way")
232                        || qName.equals("relation")) {
233                    data.put(current);
234                }
235            }
236        }
237    
238        public OsmHistoryReader(InputStream source) {
239            this.in = source;
240            this.data = new HistoryDataSet();
241        }
242    
243        public HistoryDataSet parse(ProgressMonitor progressMonitor) throws SAXException, IOException {
244            InputSource inputSource = new InputSource(new InputStreamReader(in, "UTF-8"));
245            progressMonitor.beginTask(tr("Parsing OSM history data ..."));
246            try {
247                SAXParserFactory.newInstance().newSAXParser().parse(inputSource, new Parser());
248            } catch (ParserConfigurationException e1) {
249                e1.printStackTrace(); // broken SAXException chaining
250                throw new SAXException(e1);
251            } finally {
252                progressMonitor.finishTask();
253            }
254            return data;
255        }
256    }