Package org.projectforge.xml.stream

Source Code of org.projectforge.xml.stream.XmlObjectWriter

/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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.projectforge.xml.stream;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Branch;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.tree.DefaultCDATA;
import org.projectforge.common.BeanHelper;
import org.projectforge.common.NumberHelper;
import org.projectforge.xml.stream.converter.IConverter;

/**
* Serializes objects to xml. A simple solution for streaming xml objects and to prevent default values from the xml output (because this
* feature isn't yet available in XStream). It's only fit the ProjectForge requirements and is not very useful as generic xml streaming
* package.
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class XmlObjectWriter
{
  private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(XmlObjectWriter.class);

  public static final String ATTR_ID = "o-id";

  public static final String ATTR_REF_ID = "ref-id";

  private boolean onlyAnnotatedFields;

  private AliasMap aliasMap;

  private int refIdCounter = 0;

  private XmlRegistry xmlRegistry = XmlRegistry.baseRegistry();

  /**
   * Key is the class name with the hashCode of the object, e. g.: org.projectforge.xml.stream.TestObject:987345678854 and the value is the
   * element where the object was written to.
   */
  private Map<String, Element> writtenObjects = new HashMap<String, Element>();

  /**
   * For customization, the base xml registry of XmlRegistry is used at default.
   * @param xmlRegistry
   */
  public XmlObjectWriter setXmlRegistry(final XmlRegistry xmlRegistry)
  {
    this.xmlRegistry = xmlRegistry;
    return this;
  }

  public XmlObjectWriter setAliasMap(final AliasMap aliasMap)
  {
    this.aliasMap = aliasMap;
    return this;
  }

  private AliasMap getAliasMap()
  {
    if (this.aliasMap == null) {
      this.aliasMap = new AliasMap();
    }
    return this.aliasMap;
  }

  /**
   * Writes the given object as xml.
   * @param obj
   */
  public static String writeAsXml(final Object obj, final boolean prettyFormat)
  {
    return writeAsXml(obj, null, prettyFormat);
  }

  /**
   * Writes the given object as xml.
   * @param obj
   */
  public static String writeAsXml(final Object obj)
  {
    return writeAsXml(obj, null, false);
  }

  /**
   * Writes the given object as xml.
   * @param obj
   * @param aliasMap
   */
  public static String writeAsXml(final Object obj, final AliasMap aliasMap)
  {
    return writeAsXml(obj, aliasMap, false);
  }

  /**
   * Writes the given object as xml.
   * @param obj
   * @param aliasMap
   * @param prettyFormat
   */
  public static String writeAsXml(final Object obj, final AliasMap aliasMap, final boolean prettyFormat)
  {
    final XmlObjectWriter xmlWriter = new XmlObjectWriter();
    if (aliasMap != null) {
      xmlWriter.setAliasMap(aliasMap);
    }
    return xmlWriter.writeToXml(obj, prettyFormat);
  }

  /**
   * Reader and Writer will ignore static and final fields.
   * @return true if the field has to be ignored by the writer.
   */
  public static boolean ignoreField(final Field field)
  {
    final int modifiers = field.getModifiers();
    return Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers) == true || Modifier.isTransient(modifiers);
  }

  public XmlObjectWriter setOnlyAnnotatedFields(boolean onlyAnnotatedFields)
  {
    this.onlyAnnotatedFields = onlyAnnotatedFields;
    return this;
  }

  public String writeToXml(final Object obj)
  {
    return writeToXml(obj, false);
  }

  public String writeToXml(final Object obj, final boolean prettyFormat)
  {
    final Document document = DocumentHelper.createDocument();
    final Element element = write(document, obj);
    return XmlHelper.toString(element, prettyFormat);
  }

  public Element write(final Branch parent, final Object obj)
  {
    reset();
    return write(parent, obj, null, false, false);
  }

  private void reset()
  {
    this.writtenObjects.clear();
  }

  private Element write(final Branch parent, final Object obj, String name, final boolean asAttribute, final boolean asCDATA)
  {
    if (obj == null) {
      return null;
    }
    final Class< ? > type = obj.getClass();
    if (name == null) {
      name = getAliasMap().getAliasForClass(type);
    }
    if (name == null && obj.getClass().isAnnotationPresent(XmlObject.class) == true) {
      final XmlObject xmlObject = obj.getClass().getAnnotation(XmlObject.class);
      if (StringUtils.isNotEmpty(xmlObject.alias()) == true) {
        name = xmlObject.alias();
      }
    }
    if (name == null) {
      name = xmlRegistry.getAliasForClass(type);
    }
    if (name == null) {
      name = obj.getClass().getName();
    }
    if (isRegistered(obj) == true) {
      final Element el = getRegisteredElement(obj);
      Integer refId;
      final Attribute attr = el.attribute(ATTR_ID);
      if (attr == null) {
        // Id attribute not yet written. So add this attribute:
        refId = refIdCounter++;
        el.addAttribute(ATTR_ID, String.valueOf(refId));
      } else {
        refId = NumberHelper.parseInteger(attr.getText());
      }
      if (refId == null) {
        log.error("Can't parse ref id: " + attr.getText());
      } else {
        final Element element = parent.addElement(name);
        element.addAttribute(ATTR_REF_ID, String.valueOf(refId));
        return element;
      }
    }
    IConverter< ? > converter = xmlRegistry.getConverter(type);
    if (converter != null) {
      final String sValue = converter.toString(obj);
      writeValue(parent, obj, name, sValue, asAttribute, asCDATA);
      return (Element) parent;
    } else if (Enum.class.isAssignableFrom(type) == true) {
      final String sValue = ((Enum< ? >) obj).name();
      writeValue(parent, obj, name, sValue, asAttribute, asCDATA);
      return (Element) parent;
    } else if (obj instanceof Collection< ? >) {
      final Element listElement = parent.addElement(name);
      final Iterator< ? > it = ((Collection< ? >) obj).iterator();
      while (it.hasNext() == true) {
        write(listElement, it.next(), null, false, false);
      }
      return listElement;
    }
    final Element element = parent.addElement(name);
    registerElement(obj, element);
    final Field[] fields = BeanHelper.getAllDeclaredFields(obj.getClass());
    AccessibleObject.setAccessible(fields, true);
    for (final Field field : fields) {
      if (field.isAnnotationPresent(XmlOmitField.class) == true || ignoreField(obj, field) == true) {
        continue;
      }
      final XmlField ann = field.isAnnotationPresent(XmlField.class) == true ? field.getAnnotation(XmlField.class) : null;
      if (onlyAnnotatedFields == true && field.isAnnotationPresent(XmlField.class) == false) {
        continue;
      }
      final Object value = BeanHelper.getFieldValue(obj, field);
      writeField(field, obj, value, ann, element);
    }
    return element;
  }

  protected void writeField(final Field field, final Object obj, final Object fieldValue, final XmlField annotation, final Element element)
  {
    if (fieldValue == null || isDefaultType(annotation, fieldValue) == true) {
      return;
    }
    boolean childAsAttribute = false;
    if (annotation != null) {
      if (annotation.asElement() == false && (asAttributeAsDefault(field.getType()) == true || annotation.asAttribute() == true)) {
        childAsAttribute = true;
      }
    } else if (asAttributeAsDefault(field.getType()) == true) {
      childAsAttribute = true;
    }
    final boolean childAsCDATA = (annotation != null && annotation.asCDATA() == true);
    final String childName;
    if (annotation != null && StringUtils.isNotEmpty(annotation.alias()) == true) {
      childName = annotation.alias();
    } else {
      childName = field.getName();
    }
    write(element, fieldValue, childName, childAsAttribute, childAsCDATA);
  }

  protected void addAttribute(final Element element, final Object obj, final String name, final String value)
  {
    element.addAttribute(name, value);
  }

  private void writeValue(final Branch branch, final Object obj, final String key, final String sValue, final boolean asAttribute,
      final boolean asCDATA)
  {
    if (sValue == null) {
      return;
    }
    if (asAttribute == true) {
      addAttribute((Element) branch, obj, key, sValue);
    } else if (asCDATA == true) {
      branch.addElement(key).add(new DefaultCDATA(sValue));
    } else {
      branch.addElement(key).setText(sValue);
    }
  }

  private void registerElement(final Object obj, final Element element)
  {
    writtenObjects.put(obj.getClass().getName() + ":" + obj.hashCode(), element);
  }

  private Element getRegisteredElement(final Object obj)
  {
    return writtenObjects.get(obj.getClass().getName() + ":" + obj.hashCode());
  }

  private boolean isRegistered(final Object obj)
  {
    return writtenObjects.containsKey(obj.getClass().getName() + ":" + obj.hashCode());
  }

  /**
   * Overload this method for further control. Don't forget to call super!
   * @param obj
   * @param field
   * @return true if the field has to be ignored by the writer.
   * @see #ignoreField(Field)
   */
  protected boolean ignoreField(final Object obj, final Field field)
  {
    return ignoreField(field);
  }

  /**
   * For some types (primitive types as double and int) serialization as attributes instead of elements is used.
   * @param type
   * @return true if for the given type a serialization as attribute should be used at default.
   */
  public boolean asAttributeAsDefault(final Class< ? > type)
  {
    return xmlRegistry.asAttributeAsDefault(type) || Enum.class.isAssignableFrom(type) == true;
  }

  private static boolean isDefaultType(final XmlField ann, final Object value)
  {
    if (hasDefaultType(ann, value) == false) {
      // No default value given in the annotation.
      return false;
    }
    if (value instanceof Boolean) {
      if (ann != null) {
        return ann.defaultBooleanValue() == (Boolean) value;
      } else {
        return (Boolean) value == false;
      }
    } else if (value instanceof Double) {
      return ((Double) value).compareTo(ann.defaultDoubleValue()) == 0;
    } else if (value instanceof Integer) {
      return ((Integer) value).compareTo(ann.defaultIntValue()) == 0;
    } else if (value instanceof String) {
      return ann.defaultStringValue().equals((String) value) == true;
    } else if (Enum.class.isAssignableFrom(value.getClass()) == true) {
      return ann.defaultStringValue().equals(((Enum< ? >) value).name()) == true;
    }
    return false;
  }

  /**
   * @param ann
   * @param value
   * @return True if the annotation has the default type (meaning that no value is set in the annotation as default type).
   */
  private static boolean hasDefaultType(final XmlField ann, final Object value)
  {
    if (value instanceof Boolean) {
      // Boolean values have already default type false, if nothing else declared.
      return true;
    } else if (ann == null) {
      return false;
    } else if (value instanceof Double) {
      return Double.isNaN(ann.defaultDoubleValue()) == false;
    } else if (value instanceof Integer) {
      return ann.defaultIntValue() != XmlConstants.MAGIC_INT_NUMBER;
    } else if (value instanceof String || Enum.class.isAssignableFrom(value.getClass()) == true) {
      return XmlConstants.MAGIC_STRING.equals(ann.defaultStringValue()) == false;
    }
    return false;
  }
}
TOP

Related Classes of org.projectforge.xml.stream.XmlObjectWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.