1
2
3
4
5
6
7
8 package de.keepondreaming.xml;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.lang.reflect.Method;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Date;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Stack;
20
21 import javax.xml.parsers.ParserConfigurationException;
22 import javax.xml.parsers.SAXParser;
23 import javax.xml.parsers.SAXParserFactory;
24
25 import org.xml.sax.Attributes;
26 import org.xml.sax.InputSource;
27 import org.xml.sax.SAXException;
28 import org.xml.sax.helpers.DefaultHandler;
29
30 import de.keepondreaming.xml.util.Util;
31
32 /***
33 * Reads an xml file and sets the data using the passed strategy patterns
34 *
35 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ $Log: XmlConverter.java,v $
36 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ Revision 1.1 2005/07/10 18:37:00 wintermond
37 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ Renamed Parser to XMLConverter
38 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ Major performance improvements
39 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ Support for java.util.Date, java.sql.Date and java.sql.TimeStamp as return types
40 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ Support for attributes modelled as sub tags
41 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $ Support for method getters/setter with names different from the xml names
42 * $Author: wintermond $ $Date: 2005/07/10 18:37:00 $
43 * Revision 1.4 2005/07/09 14:11:54 wintermond Introduced ability to handle
44 * subtags that contain simple values
45 *
46 * Revision 1.3 2005/07/09 09:57:58 wintermond Javadoc, moved some methods to
47 * Util class
48 *
49 */
50 public class XmlConverter extends DefaultHandler
51 {
52 private static final Object[] NO_PARAMETERS = new Object[0];
53
54 /***
55 * Default parser
56 */
57 private SAXParser parserM = null;
58
59 /***
60 * Holds the object tree
61 */
62 private Stack<Object> stackM = new Stack<Object>();
63
64 /***
65 * strategy used to obtain runtime information not resolvable via reflection
66 */
67 private AnnotationStrategy annotationStrategyM = null;
68
69 /***
70 * Strategy used to create objects and set attributes
71 */
72 private ObjectStrategy objectStrategyM = null;
73
74 /***
75 * Method cached to handle attributes that appear as subtag in xml
76 */
77 private Method stackedMethodM;
78
79 /***
80 * Element name cached to handle attributes that appear as subtag in xml
81 */
82 private String stackedElementM;
83
84 /***
85 * Converts read date strings into date objects
86 */
87 private DateHandler dateHandlerM;
88
89 /***
90 * Caches the found getter methods
91 */
92 private Map<String, Method> methodCacheM = new HashMap<String, Method>();
93
94 /***
95 * Caches for ever class the valid methods for which set operations apply
96 */
97 private Map<Class, List<Method>> validMethodsCacheM = new HashMap<Class, List<Method>>(101);
98
99 /***
100 * Default constructor
101 *
102 * @throws ParserConfigurationException
103 * @throws SAXException
104 * @throws NullPointerException If any parameter is null
105 */
106 public XmlConverter(AnnotationStrategy parserStrategy, ObjectStrategy objectStrategy) throws ParserConfigurationException, SAXException
107 {
108 if (parserStrategy == null)
109 {
110 throw new IllegalArgumentException("parserStrategy is null");
111 }
112 if (objectStrategy == null)
113 {
114 throw new IllegalArgumentException("objectStrategy is null");
115 }
116
117 SAXParserFactory factory = SAXParserFactory.newInstance();
118 parserM = factory.newSAXParser();
119 annotationStrategyM = parserStrategy;
120 objectStrategyM = objectStrategy;
121 }
122
123 /***
124 * Reads the xml from the stream and sets the data structures accordingly
125 * using reflection. The type of <rootElement> is expected to be the root
126 * node in the xml structure
127 *
128 * @param in
129 * @param rootElement Must be an interface
130 *
131 * @return An object containing the root data read from the
132 * inpustream. This object is assignable to the interface
133 * passed by <code>rootElement</code>
134 *
135 * @throws IOException
136 * Error while reading the data from the stream
137 * @throws SAXException
138 * @throws IllegalArgumentException <code>rootElement</code> is not an interface
139 */
140 @SuppressWarnings( { "unchecked" })
141 public Object convert(InputStream in, Class rootElement) throws IOException, SAXException
142 {
143 return parse(new InputSource(in), rootElement);
144 }
145
146 /***
147 * Reads the xml from the stream and sets the data structures accordingly
148 * using reflection. The type of <rootElement> is expected to be the root
149 * node in the xml structure
150 *
151 * @param in
152 * @param rootElement
153 *
154 * @return An object containing the root data read from the
155 * inpustream. This object is assignable to the interface
156 * passed by <code>rootElement</code>
157 *
158 * @throws IOException
159 * Error while reading the data from the stream
160 * @throws SAXException
161 * @throws IllegalArgumentException <code>rootElement</code> is not an interface
162 */
163 @SuppressWarnings( { "unchecked" })
164 public Object parse(InputSource in, Class rootElement) throws IOException, SAXException
165 {
166 if(!rootElement.isInterface())
167 {
168 throw new IllegalArgumentException("rootElement is not an interface");
169 }
170 Object resultM = init(rootElement);
171
172 parserM.parse(in, this);
173 return resultM;
174 }
175
176
177 /***
178 * Initializes the parser
179 *
180 * @param rootElement
181 *
182 * @return The root element of the parsed data
183 */
184 private Object init(Class rootElement)
185 {
186 objectStrategyM.init();
187 annotationStrategyM.init();
188 methodCacheM.clear();
189 validMethodsCacheM.clear();
190 if (dateHandlerM == null)
191 {
192 dateHandlerM = new DateHandler(annotationStrategyM);
193 }
194 stackedElementM = null;
195 stackedMethodM = null;
196
197 Object result = objectStrategyM.createInstance(rootElement);
198 stackM.push(result);
199 return result;
200 }
201
202
203
204
205
206
207 @Override
208 @SuppressWarnings( { "unchecked" })
209 public void characters(char[] chars, int start, int len) throws SAXException
210 {
211 String content = new String(chars, start, len).trim();
212
213 if (content.length() > 0)
214 {
215
216
217
218 Object current = stackM.peek();
219 if (stackedElementM != null)
220 {
221 Object value = content;
222 Class returnType = stackedMethodM.getReturnType();
223 if (returnType.isPrimitive())
224 {
225 value = Util.getPrimitiveObject(returnType, content);
226 }
227 else if (!String.class.isAssignableFrom(returnType))
228 {
229 value = Util.createObject(returnType, content);
230 }
231 objectStrategyM.setAttribute(current, stackedElementM, value);
232
233
234
235
236
237 }
238 else
239 {
240 String contentAttribute = annotationStrategyM.getContentAttribute(objectStrategyM.resolveInterface(current), current);
241
242 String setterAttribute = "Content";
243 if (contentAttribute != null)
244 {
245 setterAttribute = contentAttribute;
246 }
247 objectStrategyM.setAttribute(current, setterAttribute, content);
248 }
249 }
250 }
251
252
253
254
255
256
257
258 @Override
259 public void endElement(String arg0, String arg1, String elementName) throws SAXException
260 {
261 elementName = Util.capitalize(elementName);
262 if (elementName.equals(stackedElementM))
263 {
264 stackedElementM = null;
265 stackedMethodM = null;
266 }
267 else
268 {
269 stackM.pop();
270 }
271 }
272
273
274
275
276
277
278
279 @Override
280 @SuppressWarnings("unchecked")
281 public void startElement(String arg0, String arg1, String elementName, Attributes attributes) throws SAXException
282 {
283
284 Object current = stackM.peek();
285
286
287 elementName = Util.capitalize(elementName);
288
289
290
291 Class currentType = objectStrategyM.resolveInterface(current);
292
293
294
295
296
297 if (!currentType.getSimpleName().equals(elementName))
298 {
299 Method method = getGetterMethod(elementName, currentType);
300 if (method != null)
301 {
302 if (Collection.class.isAssignableFrom(method.getReturnType()))
303 {
304 try
305 {
306
307
308
309 currentType = annotationStrategyM.getGenericReturnType(method);
310 if (currentType == null)
311 {
312 throw new IllegalArgumentException(
313 "Method "
314 + method.getName()
315 + " is not annotated and returns an object derived from java.util.Collection. Please annotate method with ReturnTypeAnnotation!");
316 }
317
318
319
320
321 Collection collection = (Collection) method.invoke(current, NO_PARAMETERS);
322 current = objectStrategyM.createInstance(currentType);
323
324 collection.add(current);
325 stackM.push(current);
326 }
327 catch (Throwable e)
328 {
329 throw new IllegalStateException("Error while adding to collection", e);
330 }
331
332 }
333 else if (Map.class.isAssignableFrom(method.getReturnType()))
334 {
335 try
336 {
337
338
339
340 currentType = annotationStrategyM.getGenericReturnType(method);
341 if (currentType == null)
342 {
343 throw new IllegalArgumentException(
344 "Method "
345 + method.getName()
346 + " is not annotated and returns an object derived from java.util.Collection. Please annotate method with ReturnTypeAnnotation!");
347 }
348
349
350
351
352
353 Map map = (Map) method.invoke(current, NO_PARAMETERS);
354 current = objectStrategyM.createInstance(currentType);
355 String attribute = annotationStrategyM.getKeyAttribute(method);
356
357
358 map.put(attributes.getValue(attribute), current);
359 stackM.push(current);
360 }
361 catch (Throwable e)
362 {
363 throw new IllegalStateException("Error while adding to map", e);
364 }
365 }
366 else if (method.getReturnType().isInterface())
367 {
368 Object target = current;
369 currentType = method.getReturnType();
370 current = objectStrategyM.createInstance(currentType);
371 objectStrategyM.setAttribute(target, elementName, current);
372 stackM.push(current);
373 }
374 else
375 {
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390 stackedMethodM = method;
391 stackedElementM = elementName;
392 }
393 }
394 }
395 setAttributesOfCurrentObject(attributes, current, currentType);
396 }
397
398 /***
399 * Looks up the getter method for the current paramter type
400 *
401 * @param elementName
402 * @param currentType
403 *
404 * @return The getter method for the current paramter type
405 */
406 private Method getGetterMethod(String elementName, Class currentType)
407 {
408 String key = currentType.getName() + "." + elementName;
409
410 Method method = methodCacheM.get(key);
411 if (method == null)
412 {
413 String methodName = objectStrategyM.getMethodName(currentType, elementName, false);
414 if (methodName == null)
415 {
416 methodName = "get" + elementName;
417 }
418 try
419 {
420 method = currentType.getMethod(methodName, new Class[0]);
421 }
422 catch (Throwable e)
423 {
424 try
425 {
426 method = currentType.getMethod("get" + elementName + "s", new Class[0]);
427 }
428 catch (Throwable e1)
429 {
430 System.err.println("Could not find getter for attribute <" + elementName + ">");
431 }
432 }
433
434 if (method != null)
435 {
436 methodCacheM.put(key, method);
437 }
438 }
439 return method;
440 }
441
442 /***
443 * Sets the attributes of the current object
444 *
445 * @param attributes
446 * @param current
447 * @param currentType
448 */
449 private void setAttributesOfCurrentObject(Attributes attributes, Object current, Class currentType)
450 {
451 try
452 {
453 List<Method> validMethods = validMethodsCacheM.get(currentType);
454 if (validMethods == null)
455 {
456
457
458
459 String methodName = null;
460 validMethods = new ArrayList<Method>();
461 validMethodsCacheM.put(currentType, validMethods);
462
463 for (Method method : currentType.getMethods())
464 {
465 methodName = method.getName();
466 Class<?> returnType = method.getReturnType();
467 if (method.getParameterTypes().length == 0 && methodName.startsWith("get")
468 && (returnType.isPrimitive() || returnType.getPackage().getName().equals("java.lang")
469 || Date.class.isAssignableFrom(returnType)))
470 {
471 validMethods.add(method);
472 }
473 }
474
475 }
476 for (Method method : validMethods)
477 {
478 String methodName = method.getName();
479 Class<?> returnType = method.getReturnType();
480 String attributeName = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
481 String value = attributes.getValue(attributeName);
482 if (value != null)
483 {
484 Object typedValue = null;
485 if (returnType.isPrimitive())
486 {
487 typedValue = Util.getPrimitiveObject(returnType, value);
488 }
489 else if (Date.class.isAssignableFrom(returnType))
490 {
491 typedValue = dateHandlerM.getDateString(method, value);
492 }
493 else
494 {
495 typedValue = Util.createObject(returnType, value);
496 }
497
498 objectStrategyM.setAttribute(current, methodName.substring(3), typedValue);
499 }
500 }
501 }
502 catch (Throwable e)
503 {
504 e.printStackTrace();
505 throw new IllegalStateException("Error while applying attributes", e);
506 }
507
508 }
509
510 }