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

Last change on this file since 3966 was 3612, checked in by stoecker, 14 years ago

add XML checking for mappaint files

  • Property svn:eol-style set to native
File size: 14.2 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.Reader;
7import java.io.IOException;
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.Map;
14import java.util.NoSuchElementException;
15import java.util.Stack;
16import java.util.concurrent.ArrayBlockingQueue;
17import java.util.concurrent.BlockingQueue;
18
19import javax.xml.parsers.SAXParser;
20import javax.xml.parsers.SAXParserFactory;
21import javax.xml.transform.Source;
22import javax.xml.transform.stream.StreamSource;
23import javax.xml.validation.Schema;
24import javax.xml.validation.SchemaFactory;
25import javax.xml.validation.ValidatorHandler;
26
27import org.openstreetmap.josm.io.MirroredInputStream;
28
29import org.xml.sax.Attributes;
30import org.xml.sax.ContentHandler;
31import org.xml.sax.InputSource;
32import org.xml.sax.Locator;
33import org.xml.sax.SAXException;
34import org.xml.sax.SAXParseException;
35import org.xml.sax.XMLReader;
36import org.xml.sax.helpers.DefaultHandler;
37import org.xml.sax.helpers.XMLFilterImpl;
38
39/**
40 * An helper class that reads from a XML stream into specific objects.
41 *
42 * @author Imi
43 */
44public class XmlObjectParser implements Iterable<Object> {
45 public static class PresetParsingException extends SAXException {
46 private int columnNumber;
47 private int lineNumber;
48
49 public PresetParsingException() {
50 super();
51 }
52
53 public PresetParsingException(Exception e) {
54 super(e);
55 }
56
57 public PresetParsingException(String message, Exception e) {
58 super(message, e);
59 }
60
61 public PresetParsingException(String message) {
62 super(message);
63 }
64
65 public PresetParsingException rememberLocation(Locator locator) {
66 if (locator == null) return this;
67 this.columnNumber = locator.getColumnNumber();
68 this.lineNumber = locator.getLineNumber();
69 return this;
70 }
71
72 @Override
73 public String getMessage() {
74 String msg = super.getMessage();
75 if (lineNumber == 0 && columnNumber == 0)
76 return msg;
77 if (msg == null) {
78 msg = getClass().getName();
79 }
80 msg = msg + " " + tr("(at line {0}, column {1})", lineNumber, columnNumber);
81 return msg;
82 }
83
84 public int getColumnNumber() {
85 return columnNumber;
86 }
87
88 public int getLineNumber() {
89 return lineNumber;
90 }
91 }
92
93 public static final String lang = LanguageInfo.getLanguageCodeXML();
94 public static class Uniform<T> implements Iterable<T>{
95 private Iterator<Object> iterator;
96 /**
97 * @param klass This has to be specified since generics are ereased from
98 * class files so the JVM cannot deduce T itself.
99 */
100 public Uniform(Reader input, String tagname, Class<T> klass) {
101 XmlObjectParser parser = new XmlObjectParser();
102 parser.map(tagname, klass);
103 parser.start(input);
104 iterator = parser.iterator();
105 }
106 public Iterator<T> iterator() {
107 return new Iterator<T>(){
108 public boolean hasNext() {return iterator.hasNext();}
109 @SuppressWarnings("unchecked") public T next() {return (T)iterator.next();}
110 public void remove() {iterator.remove();}
111 };
112 }
113 }
114
115 private static class AddNamespaceFilter extends XMLFilterImpl {
116
117 private final String namespace;
118
119 public AddNamespaceFilter(String namespace) {
120 this.namespace = namespace;
121 }
122
123 @Override
124 public void startElement (String uri, String localName, String qName, Attributes atts) throws SAXException {
125 if ("".equals(uri)) {
126 super.startElement(namespace, localName, qName, atts);
127 } else {
128 super.startElement(uri, localName, qName, atts);
129 }
130
131 }
132
133 }
134
135 private class Parser extends DefaultHandler {
136 Stack<Object> current = new Stack<Object>();
137 String characters = "";
138
139 private Locator locator;
140
141 @Override
142 public void setDocumentLocator(Locator locator) {
143 this.locator = locator;
144 }
145
146 protected void throwException(Exception e) throws PresetParsingException{
147 throw new PresetParsingException(e).rememberLocation(locator);
148 }
149
150 @Override public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException {
151 if (mapping.containsKey(qname)) {
152 Class<?> klass = mapping.get(qname).klass;
153 try {
154 current.push(klass.newInstance());
155 } catch (Exception e) {
156 throwException(e);
157 }
158 for (int i = 0; i < a.getLength(); ++i) {
159 setValue(a.getQName(i), a.getValue(i));
160 }
161 if (mapping.get(qname).onStart) {
162 report();
163 }
164 if (mapping.get(qname).both)
165 {
166 try {
167 queue.put(current.peek());
168 } catch (InterruptedException e) {
169 }
170 }
171 }
172 }
173 @Override public void endElement(String ns, String lname, String qname) throws SAXException {
174 if (mapping.containsKey(qname) && !mapping.get(qname).onStart) {
175 report();
176 } else if (characters != null && !current.isEmpty()) {
177 setValue(qname, characters.trim());
178 characters = "";
179 }
180 }
181 @Override public void characters(char[] ch, int start, int length) {
182 String s = new String(ch, start, length);
183 characters += s;
184 }
185
186 private void report() {
187 try {
188 queue.put(current.pop());
189 } catch (InterruptedException e) {
190 }
191 characters = "";
192 }
193
194 private Object getValueForClass(Class<?> klass, String value) {
195 if (klass == Boolean.TYPE)
196 return parseBoolean(value);
197 else if (klass == Integer.TYPE || klass == Long.TYPE)
198 return Long.parseLong(value);
199 else if (klass == Float.TYPE || klass == Double.TYPE)
200 return Double.parseDouble(value);
201 return value;
202 }
203
204 private void setValue(String fieldName, String value) throws SAXException {
205 if (fieldName.equals("class") || fieldName.equals("default") || fieldName.equals("throw") || fieldName.equals("new") || fieldName.equals("null")) {
206 fieldName += "_";
207 }
208 try {
209 Object c = current.peek();
210 Field f = null;
211 try {
212 f = c.getClass().getField(fieldName);
213 } catch (NoSuchFieldException e) {
214 if(fieldName.startsWith(lang))
215 {
216 String locfieldName = "locale_" +
217 fieldName.substring(lang.length());
218 try {
219 f = c.getClass().getField(locfieldName);
220 } catch (NoSuchFieldException ex) {
221 }
222 }
223 }
224 if (f != null && Modifier.isPublic(f.getModifiers())) {
225 f.set(c, getValueForClass(f.getType(), value));
226 } else {
227 if(fieldName.startsWith(lang))
228 {
229 int l = lang.length();
230 fieldName = "set" + fieldName.substring(l,l+1).toUpperCase() + fieldName.substring(l+1);
231 }
232 else
233 {
234 fieldName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
235 }
236 Method[] methods = c.getClass().getDeclaredMethods();
237 for (Method m : methods) {
238 if (m.getName().equals(fieldName) && m.getParameterTypes().length == 1) {
239 m.invoke(c, new Object[]{getValueForClass(m.getParameterTypes()[0], value)});
240 return;
241 }
242 }
243 }
244 } catch (Exception e) {
245 e.printStackTrace(); // SAXException does not dump inner exceptions.
246 throwException(e);
247 }
248 }
249
250 private boolean parseBoolean(String s) {
251 return s != null &&
252 !s.equals("0") &&
253 !s.startsWith("off") &&
254 !s.startsWith("false") &&
255 !s.startsWith("no");
256 }
257
258 @Override
259 public void error(SAXParseException e) throws SAXException {
260 throwException(e);
261 }
262
263 @Override
264 public void fatalError(SAXParseException e) throws SAXException {
265 throwException(e);
266 }
267 }
268
269 private static class Entry {
270 Class<?> klass;
271 boolean onStart;
272 boolean both;
273 public Entry(Class<?> klass, boolean onStart, boolean both) {
274 super();
275 this.klass = klass;
276 this.onStart = onStart;
277 this.both = both;
278 }
279 }
280
281 private Map<String, Entry> mapping = new HashMap<String, Entry>();
282 private DefaultHandler parser;
283
284 /**
285 * The queue of already parsed items from the parsing thread.
286 */
287 private BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(10);
288
289 /**
290 * This stores one item retrieved from the queue to give hasNext a chance.
291 * So this is also the object that will be returned on the next call to next().
292 */
293 private Object lookAhead = null;
294
295 /**
296 * This object represent the end of the stream (null is not allowed as
297 * member in class Queue).
298 */
299 private Object EOS = new Object();
300
301 public XmlObjectParser() {
302 parser = new Parser();
303 }
304
305 public XmlObjectParser(DefaultHandler handler) {
306 parser = handler;
307 }
308
309 private Iterable<Object> start(final Reader in, final ContentHandler contentHandler) {
310 new Thread("XML Reader"){
311 @Override public void run() {
312 try {
313 SAXParserFactory parserFactory = SAXParserFactory.newInstance();
314 parserFactory.setNamespaceAware(true);
315 SAXParser parser = parserFactory.newSAXParser();
316 XMLReader reader = parser.getXMLReader();
317 reader.setContentHandler(contentHandler);
318 reader.parse(new InputSource(in));
319 } catch (Exception e) {
320 try {
321 queue.put(e);
322 } catch (InterruptedException e1) {
323 }
324 }
325 try {
326 queue.put(EOS);
327 } catch (InterruptedException e) {
328 }
329 }
330 }.start();
331 return this;
332 }
333
334 public Iterable<Object> start(final Reader in) {
335 return start(in, parser);
336 }
337
338 public Iterable<Object> startWithValidation(final Reader in, String namespace, String schemaSource) throws SAXException {
339 try {
340 SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
341 Schema schema = factory.newSchema(new StreamSource(new MirroredInputStream(schemaSource)));
342 ValidatorHandler validator = schema.newValidatorHandler();
343 validator.setContentHandler(parser);
344 validator.setErrorHandler(parser);
345
346 AddNamespaceFilter filter = new AddNamespaceFilter(namespace);
347 filter.setContentHandler(validator);
348 return start(in, filter);
349 } catch(IOException e) {
350 throw new SAXException(tr("Failed to load XML schema."), e);
351 }
352 }
353
354 public void map(String tagName, Class<?> klass) {
355 mapping.put(tagName, new Entry(klass,false,false));
356 }
357
358 public void mapOnStart(String tagName, Class<?> klass) {
359 mapping.put(tagName, new Entry(klass,true,false));
360 }
361
362 public void mapBoth(String tagName, Class<?> klass) {
363 mapping.put(tagName, new Entry(klass,false,true));
364 }
365
366 /**
367 * @return The next object from the xml stream or <code>null</code>,
368 * if no more objects.
369 */
370 public Object next() throws SAXException {
371 fillLookAhead();
372 if (lookAhead == EOS)
373 throw new NoSuchElementException();
374 Object o = lookAhead;
375 lookAhead = null;
376 return o;
377 }
378
379 private void fillLookAhead() throws SAXException {
380 if (lookAhead != null)
381 return;
382 try {
383 lookAhead = queue.take();
384 if (lookAhead instanceof SAXException)
385 throw (SAXException)lookAhead;
386 else if (lookAhead instanceof RuntimeException)
387 throw (RuntimeException)lookAhead;
388 else if (lookAhead instanceof Exception)
389 throw new SAXException((Exception)lookAhead);
390 } catch (InterruptedException e) {
391 throw new RuntimeException("XmlObjectParser must not be interrupted.", e);
392 }
393 }
394
395 public boolean hasNext() throws SAXException {
396 fillLookAhead();
397 return lookAhead != EOS;
398 }
399
400 public Iterator<Object> iterator() {
401 return new Iterator<Object>(){
402 public boolean hasNext() {
403 try {
404 return XmlObjectParser.this.hasNext();
405 } catch (SAXException e) {
406 e.printStackTrace();
407 throw new RuntimeException(e);
408 }
409 }
410 public Object next() {
411 try {
412 return XmlObjectParser.this.next();
413 } catch (SAXException e) {
414 e.printStackTrace();
415 throw new RuntimeException(e);
416 }
417 }
418 public void remove() {
419 throw new UnsupportedOperationException();
420 }
421 };
422 }
423}
Note: See TracBrowser for help on using the repository browser.