/* * Copyright (C) 2012 Alexey Matveev <mvaleksej@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.matveev.pomodoro4nb.data.io; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.matveev.pomodoro4nb.data.Children; import org.matveev.pomodoro4nb.data.Properties; import org.matveev.pomodoro4nb.data.Property; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * * @author Alexey Matveev */ public final class XMLPropertiesSerializer implements PropertiesSerializer { private static final List<Property<?>> EXCLUDES_LIST = Arrays.<Property<?>>asList(Properties.SerializeKey); @Override public String serialize(Properties container) throws Exception { return new InternalSerializer(container).serialize(); } @Override public Properties deserealize(String xmlString) throws Exception { return new InternalDeserializer(xmlString).deserialize(); } //<editor-fold defaultstate="collapsed" desc="Serializer"> private static final class InternalSerializer { private final Properties container; public InternalSerializer(Properties container) { this.container = container; } public String serialize() throws Exception { final Document doc = createDocument(); doc.appendChild(serializeContainer(doc, container)); return convertToString(doc); } private Element serializeContainer(final Document doc, final Properties container) { final Element element = doc.createElement(container.getProperty(Properties.SerializeKey)); for (Property<?> property : container.getProperties()) { if (!EXCLUDES_LIST.contains(property)) { element.setAttribute(property.getName(), container.getProperty(property).toString()); } } for (Properties e : container.getElements()) { element.appendChild(serializeContainer(doc, e)); } return element; } private Document createDocument() throws ParserConfigurationException { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); final DOMImplementation domImpl = builder.getDOMImplementation(); return domImpl.createDocument(null, null, null); } } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="Deserializer"> private static final class InternalDeserializer { private static final Logger LOGGER = Logger.getLogger(InternalDeserializer.class.getName()); private final String xmlString; private InternalDeserializer(String xmlString) { this.xmlString = xmlString; } public Properties deserialize() throws Exception { final Document doc = parseXMLString(xmlString); if (doc == null) { return null; } final Element rootElement = doc.getDocumentElement(); final Properties rootContainer = createContainer(rootElement); parseAttributes(rootContainer, rootElement); final List<NodeList> subElements = extractSubElements(rootElement, rootContainer); for (NodeList list : subElements) { for (int ix = 0; ix < list.getLength(); ix++) { final Element e = (Element) list.item(ix); rootContainer.addElement(deserializeContainer(e)); } } return rootContainer; } public static List<NodeList> extractSubElements(final Element element, final Properties container) throws Exception { final List<NodeList> result = new ArrayList<NodeList>(); final Children children = container.getClass().getAnnotation(Children.class); if (children != null) { final Class<? extends Properties>[] types = children.value(); for (Class<? extends Properties> type : types) { final Object instance = type.getConstructor(null).newInstance(); if (instance != null) { final String name = ((Properties) instance).getProperty(Properties.SerializeKey); final NodeList nodeList = element.getElementsByTagName(name); if (nodeList != null) { result.add(nodeList); } } } } return result; } private static Property<?> extractPropertyFrom(final Field field) { if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(Property.class)) { try { return (Property<?>) field.get(null); } catch (IllegalArgumentException ex) { LOGGER.log(Level.WARNING, "cannot extract property from field ''{0}'', Reason: {1}", new Object[]{field.getName(), ex.getMessage()}); } catch (IllegalAccessException ex) { LOGGER.log(Level.WARNING, "cannot extract property from field ''{0}'', Reason: {1}", new Object[]{field.getName(), ex.getMessage()}); } } return null; } private static void parseAttributes(Properties container, final Element element) throws SecurityException { for (Field field : container.getClass().getFields()) { final Property<?> property = extractPropertyFrom(field); if (property != null) { if (element.hasAttribute(property.getName())) { final String value = element.getAttribute(property.getName()); Object result = null; if (property.getType().isAssignableFrom(Integer.class)) { result = Integer.valueOf(value); } else if (property.getType().isAssignableFrom(Long.class)) { result = Long.valueOf(value); } else if (property.getType().isAssignableFrom(Boolean.class)) { result = Boolean.valueOf(value); } else if (property.getType().isAssignableFrom(UUID.class)) { result = UUID.fromString(value); } else if (property.getType().isEnum()) { result = Enum.valueOf((Class<Enum>) property.getType(), (String) value); } else if (property.getType().isAssignableFrom(String.class)) { result = value; } else { LOGGER.log(Level.SEVERE, "Cannot parse property ''{0}''", property); } if (result != null) { container.setProperty(property, property.getType().cast(result)); } } } } } private static Document parseXMLString(final String xml) throws ParserConfigurationException, IOException, SAXException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(new ByteArrayInputStream(xml.getBytes("UTF-8"))); } private static Properties createContainer(final Element element) throws Exception { final Class type = Class.forName(element.getAttribute(Properties.ClassType.getName())); final Object instance = type.getConstructor(null).newInstance(); return instance == null ? null : (Properties) instance; } private static Properties deserializeContainer(final Element element) throws Exception { Properties container = createContainer(element); if (container != null) { parseAttributes(container, element); List<NodeList> subElements = extractSubElements(element, container); for (NodeList list : subElements) { for (int i = 0; i < list.getLength(); i++) { container.addElement(deserializeContainer((Element) list.item(i))); } } } return container; } } //</editor-fold> private static String convertToString(final Document doc) throws TransformerConfigurationException, TransformerException { final DOMSource source = new DOMSource(doc); final Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); final StringWriter writer = new StringWriter(); final StreamResult result = new StreamResult(writer); transformer.transform(source, result); return writer.toString(); } }