/******************************************************************************* * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se). * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-3.0-standalone.html ******************************************************************************/ package se.unlogic.standardutils.xml; import org.w3c.dom.Document; import org.w3c.dom.Element; import se.unlogic.standardutils.annotations.NoAnnotatedFieldsFoundException; import se.unlogic.standardutils.arrays.ArrayUtils; import se.unlogic.standardutils.date.DateUtils; import se.unlogic.standardutils.reflection.ReflectionUtils; import se.unlogic.standardutils.string.DummyStringyfier; import se.unlogic.standardutils.string.StringUtils; import se.unlogic.standardutils.string.Stringyfier; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.concurrent.ConcurrentHashMap; public class XMLGenerator { //TODO Use weak references in this map to prevent memory leaks private static ConcurrentHashMap<Class<?>, ClassXMLInfo> FIELD_MAP = new ConcurrentHashMap<Class<?>, ClassXMLInfo>(); @SuppressWarnings("unchecked") public static Element toXML(Object bean, Document doc){ ClassXMLInfo classInfo = FIELD_MAP.get(bean.getClass()); if(classInfo == null){ Class<?> clazz = bean.getClass(); XMLElement xmlElement = clazz.getAnnotation(XMLElement.class); if(xmlElement == null){ throw new MissingXMLAnnotationException(clazz); } String elementName = xmlElement.name(); if(StringUtils.isEmpty(elementName)){ elementName = clazz.getSimpleName(); } List<FieldXMLInfo> annotatedFields = new ArrayList<FieldXMLInfo>(); Class<?> currentClazz = clazz; while (currentClazz != Object.class) { Field[] fields = currentClazz.getDeclaredFields(); for(Field field : fields){ XMLElement elementAnnotation = field.getAnnotation(XMLElement.class); if(elementAnnotation != null){ String name = elementAnnotation.name(); if(StringUtils.isEmpty(name)){ name = field.getName(); } Stringyfier valueFormatter = null; if(elementAnnotation.valueFormatter() != DummyStringyfier.class){ try { valueFormatter = elementAnnotation.valueFormatter().newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } if(Collection.class.isAssignableFrom(field.getType())){ boolean elementable = false; if(ReflectionUtils.isGenericlyTyped(field) && Elementable.class.isAssignableFrom((Class<?>) ReflectionUtils.getGenericType(field))){ elementable = true; } String childName = elementAnnotation.childName(); if(StringUtils.isEmpty(childName)){ childName = "value"; } annotatedFields.add(new FieldXMLInfo(name, field, FieldType.Element,elementAnnotation.cdata(),elementable,true,false,childName,elementAnnotation.skipChildParentElement(),valueFormatter)); }else if(field.getType().isArray()){ boolean elementable = false; if(Elementable.class.isAssignableFrom(field.getType())){ elementable = true; } String childName = elementAnnotation.childName(); if(StringUtils.isEmpty(childName)){ childName = "value"; } annotatedFields.add(new FieldXMLInfo(name, field, FieldType.Element,elementAnnotation.cdata(),elementable,false,true,childName,elementAnnotation.skipChildParentElement(),valueFormatter)); }else{ String childName = null; if(!StringUtils.isEmpty(elementAnnotation.childName())){ childName = elementAnnotation.childName(); } annotatedFields.add(new FieldXMLInfo(name, field, FieldType.Element,elementAnnotation.cdata(),Elementable.class.isAssignableFrom(field.getType()),false,false,childName,false,valueFormatter)); } ReflectionUtils.fixFieldAccess(field); } XMLAttribute attributeAnnotation = field.getAnnotation(XMLAttribute.class); if(attributeAnnotation != null){ String name = attributeAnnotation.name(); if(StringUtils.isEmpty(name)){ name = field.getName(); } Stringyfier valueFormatter = null; if(attributeAnnotation.valueFormatter() != DummyStringyfier.class){ try { valueFormatter = attributeAnnotation.valueFormatter().newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } annotatedFields.add(new FieldXMLInfo(name, field, FieldType.Attribute,false,false,false,false,null,false,valueFormatter)); ReflectionUtils.fixFieldAccess(field); } } currentClazz = currentClazz.getSuperclass(); } if(annotatedFields.isEmpty()){ throw new NoAnnotatedFieldsFoundException(clazz,XMLElement.class,XMLAttribute.class); } classInfo = new ClassXMLInfo(elementName, annotatedFields); FIELD_MAP.put(clazz, classInfo); } Element classElement = doc.createElement(classInfo.getElementName()); for(FieldXMLInfo fieldInfo : classInfo.getFields()){ Object fieldValue; try { fieldValue = fieldInfo.getField().get(bean); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } if(fieldValue == null){ continue; }else if(!fieldInfo.isList() && fieldInfo.getValueFormatter() != null){ fieldValue = fieldInfo.getValueFormatter().format(fieldValue); }else if(fieldValue instanceof Date){ fieldValue = DateUtils.DATE_TIME_FORMATTER.format(fieldValue); } if(fieldInfo.getFieldType() == FieldType.Attribute){ classElement.setAttribute(fieldInfo.getName(), fieldValue.toString()); }else if(fieldInfo.isList()){ List<?> fieldValues = (List<?>)fieldValue; if(fieldValues.isEmpty()){ continue; } Element subElement; if(fieldInfo.skipSubElement()){ subElement = classElement; }else{ subElement = doc.createElement(fieldInfo.getName()); } for(Object value : fieldValues){ if(value != null){ parseValue(fieldInfo,value,subElement,doc); } } if(!fieldInfo.skipSubElement() && subElement.hasChildNodes()){ classElement.appendChild(subElement); } }else if(fieldInfo.isArray()){ Object[] fieldValues = (Object[])fieldValue; if(ArrayUtils.isEmpty(fieldValues)){ continue; } Element subElement; if(fieldInfo.skipSubElement()){ subElement = classElement; }else{ subElement = doc.createElement(fieldInfo.getName()); } for(Object value : fieldValues){ if(value != null){ parseValue(fieldInfo,value,subElement,doc); } } if(!fieldInfo.skipSubElement() && subElement.hasChildNodes()){ classElement.appendChild(subElement); } }else if(fieldInfo.isElementable()){ Element subElement = ((Elementable)fieldValue).toXML(doc); if(subElement != null){ if(fieldInfo.getChildName() != null){ Element middleElement = doc.createElement(fieldInfo.getChildName()); classElement.appendChild(middleElement); middleElement.appendChild(subElement); }else{ classElement.appendChild(subElement); } } }else if(fieldInfo.isCDATA()){ classElement.appendChild(XMLUtils.createCDATAElement(fieldInfo.getName(), fieldValue.toString(), doc)); }else{ classElement.appendChild(XMLUtils.createElement(fieldInfo.getName(), fieldValue.toString(), doc)); } } return classElement; } private static void parseValue(FieldXMLInfo fieldInfo, Object value, Element subElement, Document doc) { if(fieldInfo.getValueFormatter() != null){ value = fieldInfo.getValueFormatter().format(value); }else if(value instanceof Date){ value = DateUtils.DATE_TIME_FORMATTER.format(value); } if(fieldInfo.isElementable()){ Element subSubElement = ((Elementable)value).toXML(doc); if(subSubElement != null){ subElement.appendChild(subSubElement); } }else{ if(fieldInfo.isCDATA()){ subElement.appendChild(XMLUtils.createCDATAElement(fieldInfo.getChildName(), value, doc)); }else{ subElement.appendChild(XMLUtils.createElement(fieldInfo.getChildName(), value, doc)); } } } }