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

Last change on this file since 7495 was 7248, checked in by bastiK, 10 years ago

reworked MirroredInputStream (renamed to CachedFile):

  • no more awkwardly open and close InputStream if you just want the underlying file (e.g. to get file inside zip file)
  • make it easier to add configuration parameters, without having endless list of parameters for the constructor (Factory style, similar to ImageProvider)

breaks plugins; see #10139

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