001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.tools; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.io.IOException; 007 import java.io.Reader; 008 import java.lang.reflect.Field; 009 import java.lang.reflect.Method; 010 import java.lang.reflect.Modifier; 011 import java.util.HashMap; 012 import java.util.Iterator; 013 import java.util.LinkedList; 014 import java.util.Map; 015 import java.util.Stack; 016 017 import javax.xml.parsers.ParserConfigurationException; 018 import javax.xml.parsers.SAXParser; 019 import javax.xml.parsers.SAXParserFactory; 020 import javax.xml.transform.stream.StreamSource; 021 import javax.xml.validation.Schema; 022 import javax.xml.validation.SchemaFactory; 023 import javax.xml.validation.ValidatorHandler; 024 025 import org.openstreetmap.josm.io.MirroredInputStream; 026 import org.xml.sax.Attributes; 027 import org.xml.sax.ContentHandler; 028 import org.xml.sax.InputSource; 029 import org.xml.sax.Locator; 030 import org.xml.sax.SAXException; 031 import org.xml.sax.SAXParseException; 032 import org.xml.sax.XMLReader; 033 import org.xml.sax.helpers.DefaultHandler; 034 import org.xml.sax.helpers.XMLFilterImpl; 035 036 /** 037 * An helper class that reads from a XML stream into specific objects. 038 * 039 * @author Imi 040 */ 041 public class XmlObjectParser implements Iterable<Object> { 042 public static class PresetParsingException extends SAXException { 043 private int columnNumber; 044 private int lineNumber; 045 046 public PresetParsingException() { 047 super(); 048 } 049 050 public PresetParsingException(Exception e) { 051 super(e); 052 } 053 054 public PresetParsingException(String message, Exception e) { 055 super(message, e); 056 } 057 058 public PresetParsingException(String message) { 059 super(message); 060 } 061 062 public PresetParsingException rememberLocation(Locator locator) { 063 if (locator == null) return this; 064 this.columnNumber = locator.getColumnNumber(); 065 this.lineNumber = locator.getLineNumber(); 066 return this; 067 } 068 069 @Override 070 public String getMessage() { 071 String msg = super.getMessage(); 072 if (lineNumber == 0 && columnNumber == 0) 073 return msg; 074 if (msg == null) { 075 msg = getClass().getName(); 076 } 077 msg = msg + " " + tr("(at line {0}, column {1})", lineNumber, columnNumber); 078 return msg; 079 } 080 081 public int getColumnNumber() { 082 return columnNumber; 083 } 084 085 public int getLineNumber() { 086 return lineNumber; 087 } 088 } 089 090 public static final String lang = LanguageInfo.getLanguageCodeXML(); 091 092 private static class AddNamespaceFilter extends XMLFilterImpl { 093 094 private final String namespace; 095 096 public AddNamespaceFilter(String namespace) { 097 this.namespace = namespace; 098 } 099 100 @Override 101 public void startElement (String uri, String localName, String qName, Attributes atts) throws SAXException { 102 if ("".equals(uri)) { 103 super.startElement(namespace, localName, qName, atts); 104 } else { 105 super.startElement(uri, localName, qName, atts); 106 } 107 108 } 109 110 } 111 112 private class Parser extends DefaultHandler { 113 Stack<Object> current = new Stack<Object>(); 114 StringBuilder characters = new StringBuilder(64); 115 116 private Locator locator; 117 118 @Override 119 public void setDocumentLocator(Locator locator) { 120 this.locator = locator; 121 } 122 123 protected void throwException(Exception e) throws PresetParsingException{ 124 throw new PresetParsingException(e).rememberLocation(locator); 125 } 126 127 @Override public void startElement(String ns, String lname, String qname, Attributes a) throws SAXException { 128 if (mapping.containsKey(qname)) { 129 Class<?> klass = mapping.get(qname).klass; 130 try { 131 current.push(klass.newInstance()); 132 } catch (Exception e) { 133 throwException(e); 134 } 135 for (int i = 0; i < a.getLength(); ++i) { 136 setValue(mapping.get(qname), a.getQName(i), a.getValue(i)); 137 } 138 if (mapping.get(qname).onStart) { 139 report(); 140 } 141 if (mapping.get(qname).both) { 142 queue.add(current.peek()); 143 } 144 } 145 } 146 @Override public void endElement(String ns, String lname, String qname) throws SAXException { 147 if (mapping.containsKey(qname) && !mapping.get(qname).onStart) { 148 report(); 149 } else if (mapping.containsKey(qname) && characters != null && !current.isEmpty()) { 150 setValue(mapping.get(qname), qname, characters.toString().trim()); 151 characters = new StringBuilder(64); 152 } 153 } 154 @Override public void characters(char[] ch, int start, int length) { 155 characters.append(ch, start, length); 156 } 157 158 private void report() { 159 queue.add(current.pop()); 160 characters = new StringBuilder(64); 161 } 162 163 private Object getValueForClass(Class<?> klass, String value) { 164 if (klass == Boolean.TYPE) 165 return parseBoolean(value); 166 else if (klass == Integer.TYPE || klass == Long.TYPE) 167 return Long.parseLong(value); 168 else if (klass == Float.TYPE || klass == Double.TYPE) 169 return Double.parseDouble(value); 170 return value; 171 } 172 173 private void setValue(Entry entry, String fieldName, String value) throws SAXException { 174 if (entry == null) { 175 throw new NullPointerException("entry cannot be null"); 176 } 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()) && String.class.equals(f.getType())) { 187 f.set(c, getValueForClass(f.getType(), value)); 188 } else { 189 if (fieldName.startsWith(lang)) { 190 int l = lang.length(); 191 fieldName = "set" + fieldName.substring(l, l + 1).toUpperCase() + fieldName.substring(l + 1); 192 } else { 193 fieldName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); 194 } 195 Method m = entry.getMethod(fieldName); 196 if (m != null) { 197 m.invoke(c, new Object[]{getValueForClass(m.getParameterTypes()[0], value)}); 198 } 199 } 200 } catch (Exception e) { 201 e.printStackTrace(); // SAXException does not dump inner exceptions. 202 throwException(e); 203 } 204 } 205 206 private boolean parseBoolean(String s) { 207 return s != null 208 && !s.equals("0") 209 && !s.startsWith("off") 210 && !s.startsWith("false") 211 && !s.startsWith("no"); 212 } 213 214 @Override 215 public void error(SAXParseException e) throws SAXException { 216 throwException(e); 217 } 218 219 @Override 220 public void fatalError(SAXParseException e) throws SAXException { 221 throwException(e); 222 } 223 } 224 225 private static class Entry { 226 Class<?> klass; 227 boolean onStart; 228 boolean both; 229 private final Map<String, Field> fields = new HashMap<String, Field>(); 230 private final Map<String, Method> methods = new HashMap<String, Method>(); 231 232 public Entry(Class<?> klass, boolean onStart, boolean both) { 233 this.klass = klass; 234 this.onStart = onStart; 235 this.both = both; 236 } 237 238 Field getField(String s) { 239 if (fields.containsKey(s)) { 240 return fields.get(s); 241 } else { 242 try { 243 Field f = klass.getField(s); 244 fields.put(s, f); 245 return f; 246 } catch (NoSuchFieldException ex) { 247 fields.put(s, null); 248 return null; 249 } 250 } 251 } 252 253 Method getMethod(String s) { 254 if (methods.containsKey(s)) { 255 return methods.get(s); 256 } else { 257 for (Method m : klass.getMethods()) { 258 if (m.getName().equals(s) && m.getParameterTypes().length == 1) { 259 methods.put(s, m); 260 return m; 261 } 262 } 263 methods.put(s, null); 264 return null; 265 } 266 } 267 } 268 269 private Map<String, Entry> mapping = new HashMap<String, Entry>(); 270 private DefaultHandler parser; 271 272 /** 273 * The queue of already parsed items from the parsing thread. 274 */ 275 private LinkedList<Object> queue = new LinkedList<Object>(); 276 private Iterator<Object> queueIterator = null; 277 278 public XmlObjectParser() { 279 parser = new Parser(); 280 } 281 282 public XmlObjectParser(DefaultHandler handler) { 283 parser = handler; 284 } 285 286 private Iterable<Object> start(final Reader in, final ContentHandler contentHandler) throws SAXException, IOException { 287 try { 288 SAXParserFactory parserFactory = SAXParserFactory.newInstance(); 289 parserFactory.setNamespaceAware(true); 290 SAXParser saxParser = parserFactory.newSAXParser(); 291 XMLReader reader = saxParser.getXMLReader(); 292 reader.setContentHandler(contentHandler); 293 reader.parse(new InputSource(in)); 294 queueIterator = queue.iterator(); 295 return this; 296 } catch (ParserConfigurationException e) { 297 // This should never happen ;-) 298 throw new RuntimeException(e); 299 } 300 } 301 302 public Iterable<Object> start(final Reader in) throws SAXException { 303 try { 304 return start(in, parser); 305 } catch (IOException e) { 306 throw new SAXException(e); 307 } 308 } 309 310 public Iterable<Object> startWithValidation(final Reader in, String namespace, String schemaSource) throws SAXException { 311 try { 312 SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); 313 Schema schema = factory.newSchema(new StreamSource(new MirroredInputStream(schemaSource))); 314 ValidatorHandler validator = schema.newValidatorHandler(); 315 validator.setContentHandler(parser); 316 validator.setErrorHandler(parser); 317 318 AddNamespaceFilter filter = new AddNamespaceFilter(namespace); 319 filter.setContentHandler(validator); 320 return start(in, filter); 321 } catch(IOException e) { 322 throw new SAXException(tr("Failed to load XML schema."), e); 323 } 324 } 325 326 public void map(String tagName, Class<?> klass) { 327 mapping.put(tagName, new Entry(klass,false,false)); 328 } 329 330 public void mapOnStart(String tagName, Class<?> klass) { 331 mapping.put(tagName, new Entry(klass,true,false)); 332 } 333 334 public void mapBoth(String tagName, Class<?> klass) { 335 mapping.put(tagName, new Entry(klass,false,true)); 336 } 337 338 public Object next() { 339 return queueIterator.next(); 340 } 341 342 public boolean hasNext() { 343 return queueIterator.hasNext(); 344 } 345 346 @Override 347 public Iterator<Object> iterator() { 348 return queue.iterator(); 349 } 350 }