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

Last change on this file since 5299 was 5135, checked in by simon04, 12 years ago

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
File size: 11.8 KB
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.