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

Last change on this file since 6796 was 6643, checked in by Don-vip, 10 years ago

global replacement of e.printStackTrace() by Main.error(e)

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