source: josm/trunk/src/org/openstreetmap/josm/tools/XmlObjectParser.java @ 5241

Revision 5135, 11.8 KB checked in by simon04, 8 weeks ago (diff)

see #6964 - speedup XmlObjectParser by caching fields/methods used to set values using reflection (speeds up preset parsing among others)

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.tools;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.io.Reader;
8import java.lang.reflect.Field;
9import java.lang.reflect.Method;
10import java.lang.reflect.Modifier;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.LinkedList;
14import java.util.Map;
15import java.util.Stack;
16
17import javax.xml.parsers.ParserConfigurationException;
18import javax.xml.parsers.SAXParser;
19import javax.xml.parsers.SAXParserFactory;
20import javax.xml.transform.stream.StreamSource;
21import javax.xml.validation.Schema;
22import javax.xml.validation.SchemaFactory;
23import javax.xml.validation.ValidatorHandler;
24
25import org.openstreetmap.josm.io.MirroredInputStream;
26import org.xml.sax.Attributes;
27import org.xml.sax.ContentHandler;
28import org.xml.sax.InputSource;
29import org.xml.sax.Locator;
30import org.xml.sax.SAXException;
31import org.xml.sax.SAXParseException;
32import org.xml.sax.XMLReader;
33import org.xml.sax.helpers.DefaultHandler;
34import org.xml.sax.helpers.XMLFilterImpl;
35
36/**
37 * An helper class that reads from a XML stream into specific objects.
38 *
39 * @author Imi
40 */
41public class XmlObjectParser implements Iterable<Object> {
42    public static class PresetParsingException extends SAXException {
43        private int columnNumber;
44        private int lineNumber;
45
46        public PresetParsingException() {
47            super();
48        }
49
50        public PresetParsingException(Exception e) {
51            super(e);
52        }
53
54        public PresetParsingException(String message, Exception e) {
55            super(message, e);
56        }
57
58        public PresetParsingException(String message) {
59            super(message);
60        }
61
62        public PresetParsingException rememberLocation(Locator locator) {
63            if (locator == null) return this;
64            this.columnNumber = locator.getColumnNumber();
65            this.lineNumber = locator.getLineNumber();
66            return this;
67        }
68
69        @Override
70        public String getMessage() {
71            String msg = super.getMessage();
72            if (lineNumber == 0 && columnNumber == 0)
73                return msg;
74            if (msg == null) {
75                msg = getClass().getName();
76            }
77            msg = msg + " " + tr("(at line {0}, column {1})", lineNumber, columnNumber);
78            return msg;
79        }
80
81        public int getColumnNumber() {
82            return columnNumber;
83        }
84
85        public int getLineNumber() {
86            return lineNumber;
87        }
88    }
89
90    public static final String lang = LanguageInfo.getLanguageCodeXML();
91
92    private static class AddNamespaceFilter extends XMLFilterImpl {
93
94        private final String namespace;
95
96        public AddNamespaceFilter(String namespace) {
97            this.namespace = namespace;
98        }
99
100        @Override
101        public void startElement (String uri, String localName, String qName, Attributes atts) throws SAXException {
102            if ("".equals(uri)) {
103                super.startElement(namespace, localName, qName, atts);
104            } else {
105                super.startElement(uri, localName, qName, atts);
106            }
107
108        }
109
110    }
111
112    private class Parser extends DefaultHandler {
113        Stack<Object> current = new Stack<Object>();
114        StringBuilder characters = new StringBuilder(64);
115
116        private Locator locator;
117
118        @Override
119        public void setDocumentLocator(Locator locator) {
120            this.locator = locator;
121        }
122
123        protected void throwException(Exception e) throws PresetParsingException{
124            throw new PresetParsingException(e).rememberLocation(locator);
125        }
126
127        @Override public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException {
128            if (mapping.containsKey(qname)) {
129                Class<?> klass = mapping.get(qname).klass;
130                try {
131                    current.push(klass.newInstance());
132                } catch (Exception e) {
133                    throwException(e);
134                }
135                for (int i = 0; i < a.getLength(); ++i) {
136                    setValue(mapping.get(qname), a.getQName(i), a.getValue(i));
137                }
138                if (mapping.get(qname).onStart) {
139                    report();
140                }
141                if (mapping.get(qname).both) {
142                    queue.add(current.peek());
143                }
144            }
145        }
146        @Override public void endElement(String ns, String lname, String qname) throws SAXException {
147            if (mapping.containsKey(qname) && !mapping.get(qname).onStart) {
148                report();
149            } else if (mapping.containsKey(qname) && characters != null && !current.isEmpty()) {
150                setValue(mapping.get(qname), qname, characters.toString().trim());
151                characters  = new StringBuilder(64);
152            }
153        }
154        @Override public void characters(char[] ch, int start, int length) {
155            characters.append(ch, start, length);
156        }
157
158        private void report() {
159            queue.add(current.pop());
160            characters  = new StringBuilder(64);
161        }
162
163        private Object getValueForClass(Class<?> klass, String value) {
164            if (klass == Boolean.TYPE)
165                return parseBoolean(value);
166            else if (klass == Integer.TYPE || klass == Long.TYPE)
167                return Long.parseLong(value);
168            else if (klass == Float.TYPE || klass == Double.TYPE)
169                return Double.parseDouble(value);
170            return value;
171        }
172
173        private void setValue(Entry entry, String fieldName, String value) throws SAXException {
174            if (entry == null) {
175                throw new NullPointerException("entry cannot be null");
176            }
177            if (fieldName.equals("class") || fieldName.equals("default") || fieldName.equals("throw") || fieldName.equals("new") || fieldName.equals("null")) {
178                fieldName += "_";
179            }
180            try {
181                Object c = current.peek();
182                Field f = entry.getField(fieldName);
183                if (f == null && fieldName.startsWith(lang)) {
184                    f = entry.getField("locale_" + fieldName.substring(lang.length()));
185                }
186                if (f != null && Modifier.isPublic(f.getModifiers()) && String.class.equals(f.getType())) {
187                    f.set(c, getValueForClass(f.getType(), value));
188                } else {
189                    if (fieldName.startsWith(lang)) {
190                        int l = lang.length();
191                        fieldName = "set" + fieldName.substring(l, l + 1).toUpperCase() + fieldName.substring(l + 1);
192                    } else {
193                        fieldName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
194                    }
195                    Method m = entry.getMethod(fieldName);
196                    if (m != null) {
197                        m.invoke(c, new Object[]{getValueForClass(m.getParameterTypes()[0], value)});
198                    }
199                }
200            } catch (Exception e) {
201                e.printStackTrace(); // SAXException does not dump inner exceptions.
202                throwException(e);
203            }
204        }
205
206        private boolean parseBoolean(String s) {
207            return s != null
208                    && !s.equals("0")
209                    && !s.startsWith("off")
210                    && !s.startsWith("false")
211                    && !s.startsWith("no");
212        }
213
214        @Override
215        public void error(SAXParseException e) throws SAXException {
216            throwException(e);
217        }
218
219        @Override
220        public void fatalError(SAXParseException e) throws SAXException {
221            throwException(e);
222        }
223    }
224
225    private static class Entry {
226        Class<?> klass;
227        boolean onStart;
228        boolean both;
229        private final Map<String, Field> fields = new HashMap<String, Field>();
230        private final Map<String, Method> methods = new HashMap<String, Method>();
231
232        public Entry(Class<?> klass, boolean onStart, boolean both) {
233            this.klass = klass;
234            this.onStart = onStart;
235            this.both = both;
236        }
237
238        Field getField(String s) {
239            if (fields.containsKey(s)) {
240                return fields.get(s);
241            } else {
242                try {
243                    Field f = klass.getField(s);
244                    fields.put(s, f);
245                    return f;
246                } catch (NoSuchFieldException ex) {
247                    fields.put(s, null);
248                    return null;
249                }
250            }
251        }
252
253        Method getMethod(String s) {
254            if (methods.containsKey(s)) {
255                return methods.get(s);
256            } else {
257                for (Method m : klass.getMethods()) {
258                    if (m.getName().equals(s) && m.getParameterTypes().length == 1) {
259                        methods.put(s, m);
260                        return m;
261                    }
262                }
263                methods.put(s, null);
264                return null;
265            }
266        }
267    }
268
269    private Map<String, Entry> mapping = new HashMap<String, Entry>();
270    private DefaultHandler parser;
271
272    /**
273     * The queue of already parsed items from the parsing thread.
274     */
275    private LinkedList<Object> queue = new LinkedList<Object>();
276    private Iterator<Object> queueIterator = null;
277
278    public XmlObjectParser() {
279        parser = new Parser();
280    }
281
282    public XmlObjectParser(DefaultHandler handler) {
283        parser = handler;
284    }
285
286    private Iterable<Object> start(final Reader in, final ContentHandler contentHandler) throws SAXException, IOException {
287        try {
288            SAXParserFactory parserFactory = SAXParserFactory.newInstance();
289            parserFactory.setNamespaceAware(true);
290            SAXParser saxParser = parserFactory.newSAXParser();
291            XMLReader reader = saxParser.getXMLReader();
292            reader.setContentHandler(contentHandler);
293            reader.parse(new InputSource(in));
294            queueIterator = queue.iterator();
295            return this;
296        } catch (ParserConfigurationException e) {
297            // This should never happen ;-)
298            throw new RuntimeException(e);
299        }
300    }
301
302    public Iterable<Object> start(final Reader in) throws SAXException {
303        try {
304            return start(in, parser);
305        } catch (IOException e) {
306            throw new SAXException(e);
307        }
308    }
309
310    public Iterable<Object> startWithValidation(final Reader in, String namespace, String schemaSource) throws SAXException {
311        try {
312            SchemaFactory factory =  SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
313            Schema schema = factory.newSchema(new StreamSource(new MirroredInputStream(schemaSource)));
314            ValidatorHandler validator = schema.newValidatorHandler();
315            validator.setContentHandler(parser);
316            validator.setErrorHandler(parser);
317
318            AddNamespaceFilter filter = new AddNamespaceFilter(namespace);
319            filter.setContentHandler(validator);
320            return start(in, filter);
321        } catch(IOException e) {
322            throw new SAXException(tr("Failed to load XML schema."), e);
323        }
324    }
325
326    public void map(String tagName, Class<?> klass) {
327        mapping.put(tagName, new Entry(klass,false,false));
328    }
329
330    public void mapOnStart(String tagName, Class<?> klass) {
331        mapping.put(tagName, new Entry(klass,true,false));
332    }
333
334    public void mapBoth(String tagName, Class<?> klass) {
335        mapping.put(tagName, new Entry(klass,false,true));
336    }
337
338    public Object next() {
339        return queueIterator.next();
340    }
341
342    public boolean hasNext() {
343        return queueIterator.hasNext();
344    }
345
346    @Override
347    public Iterator<Object> iterator() {
348        return queue.iterator();
349    }
350}
Note: See TracBrowser for help on using the repository browser.