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

Last change on this file since 8777 was 8510, checked in by Don-vip, 9 years ago

checkstyle: enable relevant whitespace checks and fix them

  • Property svn:eol-style set to native
File size: 11.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.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.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.CachedFile;
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 final String lang = LanguageInfo.getLanguageCodeXML();
46
47 private static class AddNamespaceFilter extends XMLFilterImpl {
48
49 private final String namespace;
50
51 public AddNamespaceFilter(String namespace) {
52 this.namespace = namespace;
53 }
54
55 @Override
56 public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
57 if ("".equals(uri)) {
58 super.startElement(namespace, localName, qName, atts);
59 } else {
60 super.startElement(uri, localName, qName, atts);
61 }
62
63 }
64
65 }
66
67 private class Parser extends DefaultHandler {
68 private Stack<Object> current = new Stack<>();
69 private StringBuilder characters = new StringBuilder(64);
70
71 private Locator locator;
72
73 @Override
74 public void setDocumentLocator(Locator locator) {
75 this.locator = locator;
76 }
77
78 protected void throwException(Exception e) throws XmlParsingException {
79 throw new XmlParsingException(e).rememberLocation(locator);
80 }
81
82 @Override
83 public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException {
84 if (mapping.containsKey(qname)) {
85 Class<?> klass = mapping.get(qname).klass;
86 try {
87 current.push(klass.newInstance());
88 } catch (Exception e) {
89 throwException(e);
90 }
91 for (int i = 0; i < a.getLength(); ++i) {
92 setValue(mapping.get(qname), a.getQName(i), a.getValue(i));
93 }
94 if (mapping.get(qname).onStart) {
95 report();
96 }
97 if (mapping.get(qname).both) {
98 queue.add(current.peek());
99 }
100 }
101 }
102
103 @Override
104 public void endElement(String ns, String lname, String qname) throws SAXException {
105 if (mapping.containsKey(qname) && !mapping.get(qname).onStart) {
106 report();
107 } else if (mapping.containsKey(qname) && characters != null && !current.isEmpty()) {
108 setValue(mapping.get(qname), qname, characters.toString().trim());
109 characters = new StringBuilder(64);
110 }
111 }
112
113 @Override
114 public void characters(char[] ch, int start, int length) {
115 characters.append(ch, start, length);
116 }
117
118 private void report() {
119 queue.add(current.pop());
120 characters = new StringBuilder(64);
121 }
122
123 private Object getValueForClass(Class<?> klass, String value) {
124 if (klass == Boolean.TYPE)
125 return parseBoolean(value);
126 else if (klass == Integer.TYPE || klass == Long.TYPE)
127 return Long.valueOf(value);
128 else if (klass == Float.TYPE || klass == Double.TYPE)
129 return Double.valueOf(value);
130 return value;
131 }
132
133 private void setValue(Entry entry, String fieldName, String value) throws SAXException {
134 CheckParameterUtil.ensureParameterNotNull(entry, "entry");
135 if ("class".equals(fieldName) || "default".equals(fieldName) || "throw".equals(fieldName) ||
136 "new".equals(fieldName) || "null".equals(fieldName)) {
137 fieldName += "_";
138 }
139 try {
140 Object c = current.peek();
141 Field f = entry.getField(fieldName);
142 if (f == null && fieldName.startsWith(lang)) {
143 f = entry.getField("locale_" + fieldName.substring(lang.length()));
144 }
145 if (f != null && Modifier.isPublic(f.getModifiers()) && (
146 String.class.equals(f.getType()) || boolean.class.equals(f.getType()))) {
147 f.set(c, getValueForClass(f.getType(), value));
148 } else {
149 if (fieldName.startsWith(lang)) {
150 int l = lang.length();
151 fieldName = "set" + fieldName.substring(l, l + 1).toUpperCase(Locale.ENGLISH) + fieldName.substring(l + 1);
152 } else {
153 fieldName = "set" + fieldName.substring(0, 1).toUpperCase(Locale.ENGLISH) + fieldName.substring(1);
154 }
155 Method m = entry.getMethod(fieldName);
156 if (m != null) {
157 m.invoke(c, new Object[]{getValueForClass(m.getParameterTypes()[0], value)});
158 }
159 }
160 } catch (Exception e) {
161 Main.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 Class<?> klass;
187 private boolean onStart;
188 private boolean both;
189 private final Map<String, Field> fields = new HashMap<>();
190 private final Map<String, Method> methods = new HashMap<>();
191
192 public 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 fields.put(s, null);
208 return null;
209 }
210 }
211 }
212
213 Method getMethod(String s) {
214 if (methods.containsKey(s)) {
215 return methods.get(s);
216 } else {
217 for (Method m : klass.getMethods()) {
218 if (m.getName().equals(s) && m.getParameterTypes().length == 1) {
219 methods.put(s, m);
220 return m;
221 }
222 }
223 methods.put(s, null);
224 return null;
225 }
226 }
227 }
228
229 private Map<String, Entry> mapping = new HashMap<>();
230 private DefaultHandler parser;
231
232 /**
233 * The queue of already parsed items from the parsing thread.
234 */
235 private List<Object> queue = new LinkedList<>();
236 private Iterator<Object> queueIterator = null;
237
238 /**
239 * Constructs a new {@code XmlObjectParser}.
240 */
241 public XmlObjectParser() {
242 parser = new Parser();
243 }
244
245 public XmlObjectParser(DefaultHandler handler) {
246 parser = handler;
247 }
248
249 private Iterable<Object> start(final Reader in, final ContentHandler contentHandler) throws SAXException, IOException {
250 try {
251 XMLReader reader = Utils.newSafeSAXParser().getXMLReader();
252 reader.setContentHandler(contentHandler);
253 try {
254 // Do not load external DTDs (fix #8191)
255 reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
256 } catch (SAXException e) {
257 // Exception very unlikely to happen, so no need to translate this
258 Main.error("Cannot disable 'load-external-dtd' feature: "+e.getMessage());
259 }
260 reader.parse(new InputSource(in));
261 queueIterator = queue.iterator();
262 return this;
263 } catch (ParserConfigurationException e) {
264 // This should never happen ;-)
265 throw new RuntimeException(e);
266 }
267 }
268
269 /**
270 * Starts parsing from the given input reader, without validation.
271 * @param in The input reader
272 * @return iterable collection of objects
273 * @throws SAXException if any XML or I/O error occurs
274 */
275 public Iterable<Object> start(final Reader in) throws SAXException {
276 try {
277 return start(in, parser);
278 } catch (IOException e) {
279 throw new SAXException(e);
280 }
281 }
282
283 /**
284 * Starts parsing from the given input reader, with XSD validation.
285 * @param in The input reader
286 * @param namespace default namespace
287 * @param schemaSource XSD schema
288 * @return iterable collection of objects
289 * @throws SAXException if any XML or I/O error occurs
290 */
291 public Iterable<Object> startWithValidation(final Reader in, String namespace, String schemaSource) throws SAXException {
292 SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
293 try (InputStream mis = new CachedFile(schemaSource).getInputStream()) {
294 Schema schema = factory.newSchema(new StreamSource(mis));
295 ValidatorHandler validator = schema.newValidatorHandler();
296 validator.setContentHandler(parser);
297 validator.setErrorHandler(parser);
298
299 AddNamespaceFilter filter = new AddNamespaceFilter(namespace);
300 filter.setContentHandler(validator);
301 return start(in, filter);
302 } catch (IOException e) {
303 throw new SAXException(tr("Failed to load XML schema."), e);
304 }
305 }
306
307 public void map(String tagName, Class<?> klass) {
308 mapping.put(tagName, new Entry(klass, false, false));
309 }
310
311 public void mapOnStart(String tagName, Class<?> klass) {
312 mapping.put(tagName, new Entry(klass, true, false));
313 }
314
315 public void mapBoth(String tagName, Class<?> klass) {
316 mapping.put(tagName, new Entry(klass, false, true));
317 }
318
319 public Object next() {
320 return queueIterator.next();
321 }
322
323 public boolean hasNext() {
324 return queueIterator.hasNext();
325 }
326
327 @Override
328 public Iterator<Object> iterator() {
329 return queue.iterator();
330 }
331}
Note: See TracBrowser for help on using the repository browser.