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

Last change on this file since 6040 was 5949, checked in by Don-vip, 11 years ago

fix #8661 - preset: default on check not set

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