Package org.bifrost.xmlio

Source Code of org.bifrost.xmlio.XmlWriter

package org.bifrost.xmlio;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.bifrost.util.text.StringHelper;
import org.bifrost.xmlio.config.Converter;
import org.bifrost.xmlio.config.ObjectMap;
import org.bifrost.xmlio.config.PropertyMap;
import org.bifrost.xmlio.config.XmlIOConfig;

/**
* <p>Serialize an xml representation of a graph of objects into a writer.</p>
* <p>
* Created: Feb 24, 2003<br/>
* Copyright: Copyright (c) 2003<br/>
* Assumptions: none<br/>
* Requires: nothing<br/>
* Required by: nothing<br/>
* Revision History:<br/>
* </p>
* <p>Example:</p>
* <pre>
* Config config = new Config();
* config.setNumber(123);
* config.setString("acb");
* XmlWriter writer = new XmlWriter("filename.xml");
* writer.setRootObject(config);
*
* Would produce an xml file that might look like:
* &lt;Config&gt;
*   &lt;number&gt;123&lt;/number&gt;
*   &lt;string&gt;abc&lt;/string&gt;
* &lt;/Config&gt;
* </pre>
* <p>Conventions:<br/>
* <ul>
* <li>Xml elements representing a class will be in mixed case with the beginning of each
* word being upper case.</li>
* <li>Xml elements representing an attribute will be in mixed case with the exception of the
* very first letter which will be in lower case.</li>
* <li>Objects can contain other, non-primative objects which in turn will be reflected and
* have their attributes serialized in a hierachical, recursive fashion.</li>
* <li>Objects can have a List or Set of objects returned from a getter and that List or
* Set will be iterated through, serializing each of the List/Set elements.  Note, however,
* that those elements MUST NOT be primative objects, ie you cannot have a getter called
* getString which returns a collection of String objects.</li>
* <li>An error in serialzing any object will abort writing the whole xml.</li>
* </ul>
* </p>
* @author Donald Kittle <donald@bifrost.org>
* @version 1.0
* @stereotype boundary
*/

public class XmlWriter
{
  private final static String _VERSION =
    "$Id: XmlWriter.java,v 1.21 2005/07/04 11:37:05 donald Exp $";

  private Logger logger = Logger.getLogger(this.getClass().getName());
 
  /**
   * Exception when a null object is encountered.
   */
  public static final String EX_NULL_OBJECT = "Cannot serialize a null object";
  /**
   * Exception when a primative object is found in a List or Set.
   */
  public static final String EX_PRIMATIVE = "Cannot have a 'primative' inside a Collection";
  /**
   * Exception when there is an error writing to the Writer.
   */
  public static final String EX_WRITING = "Error writing xml.";
  /**
   * Exception when there is an error calling a getter.
   */
  public static final String EX_OBJECT_GRAPH = "Error reading object graph.";
  /**
   * Exception when an attribute is null.
   */
  public static final String EX_NULL_ATTRIBUTE = "Cannot serialize a null attribute";
  /**
   * Exception when there is an error creating an output file.
   */
  public static final String EX_FILE = "Error creating output file: ";
  /**
   * Exception when there is a problem flushing or closing the writer.
   */
  public static final String EX_CLOSE = "Error closing writer: ";
  /**
   * Exception when a writer is null.
   */
  public static final String EX_NULL_WRITER = "The Writer is null";

  /**
   * The root object read from the configuration.
   */
  private Object rootObject = null;

  /**
   * The writer to output the xml to.
   */
  private Writer writer = null;
 
  /**
   * Flag to control 'beautifying' the output.
   */
  private boolean beautify = false;

  /**
   * A flag to indicate whether null values should be output or not.
   */
  private boolean outputNulls = true;

  /**
   * A flag to indicate whether 'object' and property names should have dashes
   * between the words (rather than camel-casing them.
   */
  private boolean dashedNames = false;

  /**
   * Default string to use for line separators.
   */
  private String breakString = System.getProperty("line.separator");

  /**
   * Default string to use for indentation.
   */
  private String indentString = "  ";
   /**
    * A flag indicating whether the fully qualified classname should be
    * output as an element or just the classname (without the package).
    * Defaults to just the classname without the package.
    */
   private boolean includePackageName = false;
     
  private XmlIOConfig config = XmlIOConfig.getInstance();
 
  /**
   * Constructor which initializes the XmlWriter with the supplied writer object.
   * @param writer the writer to output to.
   * @throws XmlException if there was a problem
   */
  public XmlWriter(Writer writer) throws XmlException
  {
    if(writer == null)
      throw new XmlException(EX_NULL_WRITER);
    this.writer = writer;
  } // end Constructor()
 
  /**
   * Constructor which creates a FileWriter from the supplied filename.
   * @param filename the name of the file to write to
   * @throws XmlException if there was a problem
   */
  public XmlWriter(String filename) throws XmlException
  {
    FileWriter fileWriter;
    try
    {
      fileWriter = new FileWriter(filename);
    }
    catch (IOException ioe)
    {
      throw new XmlException(EX_FILE + filename + ", " + ioe.toString());
    }
    this.writer = fileWriter;
  } // end Constructor()
 
 
  /**
   * Gets the flag which controls 'beautifying' the output.
   * @return boolean the flag controlling whether or not output should be 'beautified'
   */
  public boolean getBeautify()
  {
    return beautify;
  }

  /**
   * Gets the string which will be used to break each element/attribute line of the
   * resulting xml if beatifying is true.
   * @return String the string which will be used to break each element/attribute line
   */
  public String getBreakString()
  {
    return breakString;
  }

  /**
   * Gets the string which will be used to indent each element/attribute of the resulting
   * xml if beatifying is true.
   * @return String the string which will be used to indent each element/attribute
   */
  public String getIndentString()
  {
    return indentString;
  }

  /**
   * Get the flag indicating whether the fully qualified classname should be
    * output as an element or just the classname (without the package).
   * @return boolean whether a fully qualified classname should be output.
   */
  public boolean getIncludePackageName()
  {
    return includePackageName;
  }
 
  /**
   * Sets the flag to control 'beautifying' the output.
   * @param beautify the flag controlling whether or not output should be 'beautified'
   */
  public void setBeautify(boolean beautify)
  {
    this.beautify = beautify;
  }

  /**
   * Sets the string which will be used to break each element/attribute line of the
   * resulting xml if beatifying is true.
   * @param breakString the string which will be used to break each element/attribute line
   */
  public void setBreakString(String breakString)
  {
    this.breakString = breakString;
  }

  /**
   * Sets the string which will be used to indent each element/attribute of the resulting
   * xml if beatifying is true.
   * @param indentString the string which will be used to indent each element/attribute
   */
  public void setIndentString(String indentString)
  {
    this.indentString = indentString;
  }
 
  /**
   * Set the flag indicating whether the fully qualified classname should be
    * output as an element or just the classname (without the package).
   * @param rootObject
   * @throws XmlException
   */
  public void setIncludePackageName(boolean includePackageName)
  {
    this.includePackageName = includePackageName;
  }

  /**
   * Sets the root object to be serialized out and initiates the process of writing xml
   * out to the writer.  Any attributes that are not a primative nor an Object that
   * represents a primative will be serialized as an object with 'primative' attributes.
   * @param rootObject the root object of the graph that will be serialized to xml
   * @throws XmlException if there was a problem
   */
  public void setRootObject(Object rootObject) throws XmlException
  {
    if(writer == null)
      throw new XmlException(EX_NULL_WRITER);
    this.rootObject = rootObject;
    if (breakString == null)
      breakString = "";
    if (indentString == null)
      indentString = "";
    writeObject(rootObject, writer, 0);
    try
    {
      writer.flush();
      writer.close();
    }
    catch (IOException ioe)
    {
      // Try to close again, in case it was flush that threw a fit...
      try
      {
        writer.close();
      }
      catch (IOException e)
      {
        throw new XmlException(EX_CLOSE + e.toString());
      }
    }
  } // end setRootObject()
 
  /**
   * Serializes an object out to the writer.
   * Revisions:
   *   2004-02-25: added support for NULL attribute values
   * @param object the object to serialize to xml
   * @param writer the Writer to output to
   * @param indentLevel an integer specifying the number of 'indents' for this object
   * @throws XmlException if there was a problem
   */
  private void writeObject(Object object, Writer writer, int indentLevel)
    throws XmlException
  {
    if (object == null)
      throw new XmlException(EX_NULL_OBJECT);
   
    String fqClassName = object.getClass().getName();
    String className = StringHelper.classNameWithoutPackage(fqClassName);

    // Look up the object via it's short name
    ObjectMap oMap = config.getObjectMapByName(className);
    // If not found, look for it via it's fully qualified name
    if (oMap == null)
      oMap = config.getObjectMapByName(fqClassName);
    // If not found, create a mapping from the object's class
    if (oMap == null)
      oMap = config.addClassAsMappedObject(object.getClass());
   
    if (oMap == null)
      throw new XmlException ("Cannot find nor create definition for " + className);

    className = oMap.getXmlName();
    if (dashedNames == true)
      className = StringHelper.capitalsToDashes(className);
   
    logger.fine("Writing object " + oMap.getName() + " as <" + className + ">");

    try
    {
      if (beautify == true)
        outputIndent(indentLevel, writer);
      writer.write("<");
      if (includePackageName == true)
        writer.write(fqClassName);
      else
        writer.write(className);
     
      boolean openTagWritten = true;
      boolean closeTagWritten = false;

      writeProperties(writer, indentLevel, object, oMap, true);

      writer.write(">");
      if (beautify == true)
        writer.write(breakString);

      writeProperties(writer, indentLevel, object, oMap, false);     
     
      if (closeTagWritten == false)
      { 
        if (beautify == true)
          outputIndent(indentLevel, writer);
        writer.write("</");
        if (includePackageName == true)
          writer.write(fqClassName);
        else
          writer.write(className);
        writer.write(">");
        if (beautify == true)
          writer.write(breakString);
      }
    }
    catch (IOException e)
    {
      throw new XmlException(EX_WRITING + e.toString());
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new XmlException(EX_OBJECT_GRAPH + e.toString());
    }
  
  } // end writeObject()
 
  /**
   * @param writer
   * @param indentLevel
   * @param object
   * @param oMap
   * @throws XmlException
   * @throws IllegalAccessException
   * @throws InvocationTargetException
   */
  private void writeProperties(Writer writer, int indentLevel,
      Object object, ObjectMap oMap, boolean attributes)
    throws XmlException, IllegalAccessException, InvocationTargetException
  {
    List properties = oMap.getPropertyMap();
    for (Iterator i = properties.iterator(); i.hasNext();)
    {
      PropertyMap pMap = (PropertyMap)i.next();
      // If there is a property map for this attribute at the 'global'
      // level, use that one instead
      if (config.getPropertyMapByName(pMap.getPropertyName()) != null)
        pMap = config.getPropertyMapByName(pMap.getPropertyName());

      if (pMap.getWriteAsAttribute() != attributes)
        continue;
      logger.fine("Writing object property " + pMap.getPropertyName() +
          " as xml element " + pMap.getPropertyXmlName() + ".");
      Method method = pMap.getGetter();
      if (method == null)
      {
        String name = pMap.getPropertyName();

        // If the setter method is undefined for this property map, then the
        // property map was probably added to config programmatically or through
        // a config file, rather than being generated from a class.
        if (method == null && pMap != null)
        {
          // Guess what the setter might be
          method = pMap.guessGetter(object);
          // And cache the method if it's not a global property map
          if (method != null)
            pMap.setGetter(method);
        }

        if (pMap == null)
          throw new XmlException("Lost property definition for " +
              name);
        if (method == null)
          throw new XmlException("Cannot find getter method for " + name +
              " in the object " + object.getClass().getName());
      }
      Object value = method.invoke(object, (Object[])null);
      String type = "unknown";
      Class typeClass = null;
      if (value == null)
      {
        typeClass = pMap.getPropertyType();
        type = typeClass.getName();
      }
      if (value != null)
      {
        type = value.getClass().getName();
        typeClass = value.getClass();
        // 'Correct' the type if we are dealing with a List or Set
        if ((value instanceof List || value instanceof Set) &&
            ((Collection)value).size() > 0 &&
            ((Collection)value).iterator().hasNext() == true)
        {
          if (((Collection)value).iterator().next() != null)
          {
            type =
              (((Collection)value).iterator().next()).getClass().getName();
            typeClass = (((Collection)value).iterator().next()).getClass();
          }
          // Yikes, we have a collection with at least one element, but the
          // first element is a NULL!  We should just skip this whole
          // property.
          else
            continue;
        }
        // 'Correct' the type if we are dealing with an array
        else if (value.getClass().isArray() == true &&
            ((Object[])value).length > 0)
        {
          type = ((Object[])value)[0].getClass().getName();
          typeClass = ((Object[])value)[0].getClass();
        }

        // If the properties 'actual' type is a primative, our getter will
        // actually return the Object wrapper for the primative so we must
        // update our PropertyMap to reflect this.
        if (pMap.getPropertyType() == null ||
            !type.equals(pMap.getPropertyType().getName()))
          pMap.setPropertyType(typeClass);
       
        type = pMap.getPropertyType().getName();
      }
      boolean isAttribute = (config.getConverter(type) != null);
      if (pMap.getConverter() != null)
        isAttribute = true;
     
      if (isAttribute == false && attributes == true)
      {
        pMap.setWriteAsAttribute(false);
        continue;
      }
     
      // If it's an object and it's null, don't write it out.
      if (value == null && isAttribute == false)
        continue;
     
      // Are we supposed to output nulls or ignore them.
      if (value == null && outputNulls == false)
        continue;

      // Is this null or a primative??
      if (value == null ||
        (isAttribute == true && value.getClass().isArray() == false) &&
        !(value instanceof Collection))
      {
        writeAttribute(pMap, writer, indentLevel, value, attributes);
      }
      // Is this an array of primatives??
      else if (isAttribute == true && value.getClass().isArray() == true)
      {
        Object[] array = (Object[])value;
        for (int l = 0; l < array.length; l++)
        {
          value = array[l];
          type = value.getClass().getName();
          writeAttribute(pMap, writer, indentLevel, value);
        }
        value = array;
      }
      // Is this is a list of primatives (with at least one entry in the list)??
      else if ((value instanceof Collection) && ((Collection)value).size() > 0 &&
          ((Collection)value).iterator().hasNext() == true &&
          isAttribute == true)
      {
        Collection coll = (Collection)value;
        for (Iterator inner = ((Collection)value).iterator(); inner.hasNext(); )
        {
          value = inner.next();
          writeAttribute(pMap, writer, indentLevel, value);
        }
        value = coll;
      }
      // Is this a list of objects??
      else if (value instanceof Collection)
      {
        for (Iterator inner = ((Collection)value).iterator(); inner.hasNext(); )
        {
          Object subObject = inner.next();
//            if (isAttribute(subObject) == true)
//              throw new XmlException(EX_PRIMATIVE);
          writeObject(subObject, writer, indentLevel + 1);
        }
      }
      // Is this an array of objects??
      else if (value.getClass().isArray() == true)
      {
        Object[] array = (Object[])value;
        for (int l = 0; l < array.length; l++)
        {
          value = array[l];
          writeObject(value, writer, indentLevel + 1);
        }
        value = array;
      }
      // Else write an object...
      else
        writeObject(value, writer, indentLevel + 1);
    } // end loop through all readible attributes

  }

  private void writeAttribute(PropertyMap pmap, Writer writer,
      int indentLevel, Object value, 
      boolean writeAsAttribute)
    throws XmlException
  {
    outputAttribute(pmap, value, writer, indentLevel + 1,
        true, writeAsAttribute);
  }

  private void writeAttribute(PropertyMap pmap, Writer writer,
      int indentLevel, Object value)
    throws XmlException
  {
    outputAttribute(pmap, value, writer, indentLevel + 1);
  }

  private String getAttributeName(String methodName)
  {
    int index = 3;
    if(methodName.startsWith("is"))
      index = 2;
    StringBuffer temp = new
      StringBuffer(String.valueOf(Character.toLowerCase(methodName.charAt(index))));
    index++;
    if (methodName.length() > index)
      temp.append(methodName.substring(index));
    return temp.toString();
  } // end writeAttribute()


  /**
   * Serializes an attribute out to the writer.
   * @param name the name of the attribute to serialize to xml
   * @param value the value of the attribute
   * @param writer the Writer to output to
   * @param indentLevel an integer specifying the number of 'indents' for this attribute
   * @throws XmlException if there was a problem
   */
  private void outputAttribute(PropertyMap pmap, Object value, Writer writer, int indentLevel)
    throws XmlException
  {
    outputAttribute(pmap, value, writer, indentLevel, true, false);
  } // end outputAttribute()

  /**
   * Serializes an attribute out to the writer.
   * Revisions:
   *   2004-02-25: added support for NULL attribute values
   * @param name the name of the attribute to serialize to xml
   * @param value the value of the attribute
   * @param writer the Writer to output to
   * @param indentLevel an integer specifying the number of 'indents' for this
   * attribute
   * @param writeOpenTag flag indicating whether the opening tag of the
   * attribute should be output
   * @throws XmlException if there was a problem
   */
  private void outputAttribute(PropertyMap pmap, Object value,
                              Writer writer, int indentLevel,
                              boolean writeOpenTag)
    throws XmlException
  {
    outputAttribute(pmap, value, writer, indentLevel, writeOpenTag,
        false);
  } // end outputAttribute

  /**
   * Serializes an attribute out to the writer.
   * Revisions:
   *   2004-02-25: added support for NULL attribute values
   * @param name the name of the attribute to serialize to xml
   * @param value the value of the attribute
   * @param writer the Writer to output to
   * @param indentLevel an integer specifying the number of 'indents' for this
   * attribute
   * @param writeAsAttribute flag indicating whether the the it should be
   * output as an XML attribute or an XML element
   * @throws XmlException if there was a problem
   */
  private void outputAttribute(PropertyMap pmap, Object value,
                              Writer writer, int indentLevel,
                              boolean writeOpenTag, boolean writeAsAttribute)
    throws XmlException
  {
    if (pmap == null || pmap.getPropertyXmlName() == null)
      throw new XmlException(EX_NULL_ATTRIBUTE);

    String name = pmap.getPropertyXmlName();
   
    // Look up the alias for this attribute name and use it, if it exists
    Converter converter = null;
    if (pmap.getPropertyXmlName() != null)
      name = pmap.getPropertyXmlName();
    converter = pmap.getConverter();
   
    if (converter == null)
    {
      converter = config.getConverter(pmap.getPropertyType().getName());
      if (converter != null)
        logger.finer("Using global converter.");
    }
    else
      logger.finer("Using property specific converter.");
   
    if (converter == null)
    {
      writeAsAttribute = false;
      name = StringHelper.capitalizeFirst(name);
    }
   
    try
    {
      if (dashedNames == true)
        name = StringHelper.capitalsToDashes(name);
     
      if (writeOpenTag == true)
      { 
        if (beautify == true && writeAsAttribute == false)
          outputIndent(indentLevel, writer);
        if (writeAsAttribute == false)
          writer.write("<");
        else
          writer.write(" ");
        writer.write(name);
        if (writeAsAttribute == false)
          writer.write(">");
        else
          writer.write("=\"");
      }
      if (value != null)
        writer.write(printValue(converter, value));
      else
        writer.write("<null/>");

      if (writeAsAttribute == false)
      {
        writer.write("</");
        writer.write(name);
        writer.write(">");
      }
      else
        writer.write("\"");
       
      if (beautify == true && writeAsAttribute == false)
        writer.write(breakString);
    }
    catch (Exception e)
    {
      throw new XmlException(EX_WRITING + e.toString());
    }
  } // end outputAttribute()

  private String printValue(Converter converter, Object value)
    throws XmlException
  {
    if (converter == null)
      throw new IllegalArgumentException("Converter cannot be null");
    if (value == null)
      throw new IllegalArgumentException("Value cannot be null");
       
    return converter.print(value);
  }
 
  private void outputIndent(int indentLevel, Writer writer) throws XmlException
  {
    if (indentLevel == 0)
      return;
     
    StringBuffer indent = new StringBuffer(indentString);
    for (int i = 0; i < indentLevel - 1; i++)
      indent.append(indentString);
    try
    {
      writer.write(indent.toString());
    }
    catch (IOException e)
    {
      throw new XmlException("Error writing xml." + e.toString());
    }
  } // end writeIndent
   
  /**
   * Returns true if an object is of type String.
   * @return boolean true if one of those types, otherwise false
   */
  private boolean isStringAttribute(Object value)
  {
    if (value == null)
      return false;
    String className = value.getClass().getName();
    if (className.indexOf("java.lang.String") >= 0)
      return true;
    return false;
  } // end isAttribute()
 
  /**
   * @return Returns the configuration.
   */
  public XmlIOConfig getConfig()
  {
    return config;
  }
  /**
   * @param config The new configuration.
   */
  public void setConfig(XmlIOConfig config)
  {
    this.config = config;
  }

  public boolean getOutputNulls()
  {
    return outputNulls;
  }

  public void setOutputNulls(boolean outputNulls)
  {
    this.outputNulls = outputNulls;
  }

  public boolean getDashedNames()
  {
    return dashedNames;
  }

  public void setDashedNames(boolean dashedNames)
  {
    this.dashedNames = dashedNames;
  }
 
} // end XmlWriter Class
TOP

Related Classes of org.bifrost.xmlio.XmlWriter

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.