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

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

fix #17027 - clean output of TaggingPresetPreferenceTestIT

  • Property svn:eol-style set to native
File size: 11.9 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.parsers.ParserConfigurationException;
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.CachedFile;
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 /**
44 * The language prefix to use
45 */
46 public static final String lang = LanguageInfo.getLanguageCodeXML();
47
48 private static class AddNamespaceFilter extends XMLFilterImpl {
49
50 private final String namespace;
51
52 AddNamespaceFilter(String namespace) {
53 this.namespace = namespace;
54 }
55
56 @Override
57 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
58 if ("".equals(uri)) {
59 super.startElement(namespace, localName, qName, atts);
60 } else {
61 super.startElement(uri, localName, qName, atts);
62 }
63 }
64 }
65
66 private class Parser extends DefaultHandler {
67 private final Stack<Object> current = new Stack<>();
68 private StringBuilder characters = new StringBuilder(64);
69
70 private Locator locator;
71
72 @Override
73 public void setDocumentLocator(Locator locator) {
74 this.locator = locator;
75 }
76
77 protected void throwException(Exception e) throws XmlParsingException {
78 throw new XmlParsingException(e).rememberLocation(locator);
79 }
80
81 @Override
82 public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException {
83 if (mapping.containsKey(qname)) {
84 Class<?> klass = mapping.get(qname).klass;
85 try {
86 current.push(klass.getConstructor().newInstance());
87 } catch (ReflectiveOperationException e) {
88 throwException(e);
89 }
90 for (int i = 0; i < a.getLength(); ++i) {
91 setValue(mapping.get(qname), a.getQName(i), a.getValue(i));
92 }
93 if (mapping.get(qname).onStart) {
94 report();
95 }
96 if (mapping.get(qname).both) {
97 queue.add(current.peek());
98 }
99 }
100 }
101
102 @Override
103 public void endElement(String ns, String lname, String qname) throws SAXException {
104 if (mapping.containsKey(qname) && !mapping.get(qname).onStart) {
105 report();
106 } else if (mapping.containsKey(qname) && characters != null && !current.isEmpty()) {
107 setValue(mapping.get(qname), qname, characters.toString().trim());
108 characters = new StringBuilder(64);
109 }
110 }
111
112 @Override
113 public void characters(char[] ch, int start, int length) {
114 characters.append(ch, start, length);
115 }
116
117 private void report() {
118 queue.add(current.pop());
119 characters = new StringBuilder(64);
120 }
121
122 private Object getValueForClass(Class<?> klass, String value) {
123 if (klass == Boolean.TYPE)
124 return parseBoolean(value);
125 else if (klass == Integer.TYPE || klass == Long.TYPE)
126 return Long.valueOf(value);
127 else if (klass == Float.TYPE || klass == Double.TYPE)
128 return Double.valueOf(value);
129 return value;
130 }
131
132 private void setValue(Entry entry, String fieldName, String value) throws SAXException {
133 CheckParameterUtil.ensureParameterNotNull(entry, "entry");
134 if ("class".equals(fieldName) || "default".equals(fieldName) || "throw".equals(fieldName) ||
135 "new".equals(fieldName) || "null".equals(fieldName)) {
136 fieldName += '_';
137 }
138 try {
139 Object c = current.peek();
140 Field f = entry.getField(fieldName);
141 if (f == null && fieldName.startsWith(lang)) {
142 f = entry.getField("locale_" + fieldName.substring(lang.length()));
143 }
144 if (f != null && Modifier.isPublic(f.getModifiers()) && (
145 String.class.equals(f.getType()) || boolean.class.equals(f.getType()))) {
146 f.set(c, getValueForClass(f.getType(), value));
147 } else {
148 String setter;
149 if (fieldName.startsWith(lang)) {
150 int l = lang.length();
151 setter = "set" + fieldName.substring(l, l + 1).toUpperCase(Locale.ENGLISH) + fieldName.substring(l + 1);
152 } else {
153 setter = "set" + fieldName.substring(0, 1).toUpperCase(Locale.ENGLISH) + fieldName.substring(1);
154 }
155 Method m = entry.getMethod(setter);
156 if (m != null) {
157 m.invoke(c, getValueForClass(m.getParameterTypes()[0], value));
158 }
159 }
160 } catch (ReflectiveOperationException | IllegalArgumentException e) {
161 Logging.error(e); // SAXException does not dump inner exceptions.
162 throwException(e);
163 }
164 }
165
166 private boolean parseBoolean(String s) {
167 return s != null
168 && !"0".equals(s)
169 && !s.startsWith("off")
170 && !s.startsWith("false")
171 && !s.startsWith("no");
172 }
173
174 @Override
175 public void error(SAXParseException e) throws SAXException {
176 throwException(e);
177 }
178
179 @Override
180 public void fatalError(SAXParseException e) throws SAXException {
181 throwException(e);
182 }
183 }
184
185 private static class Entry {
186 private final Class<?> klass;
187 private final boolean onStart;
188 private final boolean both;
189 private final Map<String, Field> fields = new HashMap<>();
190 private final Map<String, Method> methods = new HashMap<>();
191
192 Entry(Class<?> klass, boolean onStart, boolean both) {
193 this.klass = klass;
194 this.onStart = onStart;
195 this.both = both;
196 }
197
198 Field getField(String s) {
199 if (fields.containsKey(s)) {
200 return fields.get(s);
201 } else {
202 try {
203 Field f = klass.getField(s);
204 fields.put(s, f);
205 return f;
206 } catch (NoSuchFieldException ex) {
207 Logging.trace(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 final Map<String, Entry> mapping = new HashMap<>();
231 private final DefaultHandler parser;
232
233 /**
234 * The queue of already parsed items from the parsing thread.
235 */
236 private final List<Object> queue = new LinkedList<>();
237 private Iterator<Object> queueIterator;
238
239 /**
240 * Constructs a new {@code XmlObjectParser}.
241 */
242 public XmlObjectParser() {
243 parser = new Parser();
244 }
245
246 private Iterable<Object> start(final Reader in, final ContentHandler contentHandler) throws SAXException, IOException {
247 try {
248 XMLReader reader = XmlUtils.newSafeSAXParser().getXMLReader();
249 reader.setContentHandler(contentHandler);
250 try {
251 // Do not load external DTDs (fix #8191)
252 reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
253 } catch (SAXException e) {
254 // Exception very unlikely to happen, so no need to translate this
255 Logging.log(Logging.LEVEL_ERROR, "Cannot disable 'load-external-dtd' feature:", e);
256 }
257 reader.parse(new InputSource(in));
258 queueIterator = queue.iterator();
259 return this;
260 } catch (ParserConfigurationException e) {
261 throw new JosmRuntimeException(e);
262 }
263 }
264
265 /**
266 * Starts parsing from the given input reader, without validation.
267 * @param in The input reader
268 * @return iterable collection of objects
269 * @throws SAXException if any XML or I/O error occurs
270 */
271 public Iterable<Object> start(final Reader in) throws SAXException {
272 try {
273 return start(in, parser);
274 } catch (IOException e) {
275 throw new SAXException(e);
276 }
277 }
278
279 /**
280 * Starts parsing from the given input reader, with XSD validation.
281 * @param in The input reader
282 * @param namespace default namespace
283 * @param schemaSource XSD schema
284 * @return iterable collection of objects
285 * @throws SAXException if any XML or I/O error occurs
286 */
287 public Iterable<Object> startWithValidation(final Reader in, String namespace, String schemaSource) throws SAXException {
288 SchemaFactory factory = XmlUtils.newXmlSchemaFactory();
289 try (CachedFile cf = new CachedFile(schemaSource); InputStream mis = cf.getInputStream()) {
290 Schema schema = factory.newSchema(new StreamSource(mis));
291 ValidatorHandler validator = schema.newValidatorHandler();
292 validator.setContentHandler(parser);
293 validator.setErrorHandler(parser);
294
295 AddNamespaceFilter filter = new AddNamespaceFilter(namespace);
296 filter.setContentHandler(validator);
297 return start(in, filter);
298 } catch (IOException e) {
299 throw new SAXException(tr("Failed to load XML schema."), e);
300 }
301 }
302
303 /**
304 * Add a new tag name to class type mapping
305 * @param tagName The tag name that should be converted to that class
306 * @param klass The class the XML elements should be converted to.
307 */
308 public void map(String tagName, Class<?> klass) {
309 mapping.put(tagName, new Entry(klass, false, false));
310 }
311
312 public void mapOnStart(String tagName, Class<?> klass) {
313 mapping.put(tagName, new Entry(klass, true, false));
314 }
315
316 public void mapBoth(String tagName, Class<?> klass) {
317 mapping.put(tagName, new Entry(klass, false, true));
318 }
319
320 /**
321 * Get the next element that was parsed
322 * @return The next object
323 */
324 public Object next() {
325 return queueIterator.next();
326 }
327
328 /**
329 * Check if there is a next parsed object available
330 * @return <code>true</code> if there is a next object
331 */
332 public boolean hasNext() {
333 return queueIterator.hasNext();
334 }
335
336 @Override
337 public Iterator<Object> iterator() {
338 return queue.iterator();
339 }
340}
Note: See TracBrowser for help on using the repository browser.