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

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

Rework console output:

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