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

Last change on this file since 9185 was 9078, checked in by Don-vip, 8 years ago

sonar - Immutable Field

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