Package com.skaringa.javaxml.serializers

Source Code of com.skaringa.javaxml.serializers.ObjectSerializer

package com.skaringa.javaxml.serializers;

import java.io.PrintStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.xml.sax.Attributes;

import com.skaringa.javaxml.DeserializerException;
import com.skaringa.javaxml.PropertyKeys;
import com.skaringa.javaxml.SerializerException;
import com.skaringa.javaxml.handler.AttrImpl;
import com.skaringa.javaxml.handler.DocumentOutputHandlerInterface;
import com.skaringa.javaxml.handler.sax.ObjectDeserializerHolder;
import com.skaringa.javaxml.impl.NSConstants;
import com.skaringa.javaxml.impl.PropertyHelper;
import com.skaringa.util.Log;

/**
* Implementation of ComponentSerializer for complex objects.
*/
public final class ObjectSerializer extends AbstractSerializer {

  private String _xmlTypeName;
  private String _javaClassName;

  private static final String INNER_CLASS_TAG_XML = "._i.";
  private static final String INNER_CLASS_TAG_JAVA = "$";

  /**
   * Construct an ObjectSerializer for a concrete type.
   *
   * @param xmlTypeName
   *          The XML type name.
   */
  ObjectSerializer(String xmlTypeName) {
    _xmlTypeName = fixXMLTypeNameForInnerClass(xmlTypeName).intern();
    _javaClassName = fixJavaTypeNameForInnerClass(_xmlTypeName).intern();
  }

  /**
   * Correct the xml type name of inner classes because '$' isn't a valid XML
   * NCNameChar.
   *
   * @param javaTypeName
   *          The Java type name.
   * @return The XML type name with all occurences of '$' replaced by '._i.'.
   */
  public static String fixXMLTypeNameForInnerClass(String javaTypeName) {
    return subst(javaTypeName, INNER_CLASS_TAG_JAVA, INNER_CLASS_TAG_XML);
  }

  /**
   * Correct the java type name of inner classes. It does the reverse of
   * {@link ObjectSerializer#fixXMLTypeNameForInnerClass(String)}
   *
   * @param xmlTypeName
   *          The XML type name.
   * @return The Java type name with all occurences of '._i.' replaced by '$'.
   */
  public static String fixJavaTypeNameForInnerClass(String xmlTypeName) {
    return subst(xmlTypeName, INNER_CLASS_TAG_XML, INNER_CLASS_TAG_JAVA);
  }

  /**
   * @see ComponentSerializer#serialize(Object, Class, String, Map, Map,
   *      DocumentOutputHandlerInterface)
   */
  public void serialize(Object obj, Class type, String name, Map propertyMap,
      Map objectIdMap, DocumentOutputHandlerInterface output)
      throws SerializerException {

    boolean omitXsi = PropertyHelper.parseBoolean(propertyMap,
        PropertyKeys.OMIT_XSI_TYPE);
    boolean omitNil = PropertyHelper.parseBoolean(propertyMap,
        PropertyKeys.OMIT_XSI_NIL);
    boolean omitId = PropertyHelper.parseBoolean(propertyMap,
        PropertyKeys.OMIT_ID);

    boolean isIdRef = false;

    AttrImpl attrs = new AttrImpl();
    if (obj != null) {
      if (omitId) {
        // need to call always writeReplace
        // if object ids and references can't be written
        obj = writeReplaceObject(obj);
      } else {
        // check if this object was already serialized
        Integer id = (Integer) objectIdMap.get(obj);
        if (id != null) {
          // yes, provide only the reference of the object
          // that was already serialized
          isIdRef = true;
        } else {
          // this is the first time this objects is going to be serialized
          // provide an id for this object
          id = new Integer(objectIdMap.size());
          objectIdMap.put(obj, id);
          obj = writeReplaceObject(obj);
        }

        attrs.addAttribute(isIdRef ? "reference" : "id", new StringBuffer("i")
            .append(id).toString());
      }
    }
    if (!omitXsi) {
      attrs.addAttribute(NSConstants.SCHEMA_INSTANCE_NS_NAME,
          NSConstants.SCHEMA_INSTANCE_NS_PREFIX, "type", _xmlTypeName);
    }
    if (obj == null && !omitNil) {
      attrs.addAttribute(NSConstants.SCHEMA_INSTANCE_NS_NAME,
          NSConstants.SCHEMA_INSTANCE_NS_PREFIX, "nil", "true");
    }

    output.startElement(name, attrs);

    if (obj != null && !isIdRef) {
      inspectObject(obj, obj.getClass(), propertyMap, objectIdMap, output);
    }
    output.endElement(name);
  }

  /**
   * @see ComponentSerializer#getXMLTypeName()
   */
  public String getXMLTypeName() {
    return _xmlTypeName;
  }

  /**
   * @see ComponentSerializer#writeXMLTypeDefinition(Class, Map,
   *      DocumentOutputHandlerInterface)
   */
  public void writeXMLTypeDefinition(Class type, Map propertyMap,
      DocumentOutputHandlerInterface output) throws SerializerException {

    AttrImpl attrs = new AttrImpl();
    attrs.addAttribute("name", _xmlTypeName);
    output.startElement("xsd:complexType", attrs);
    processExtensionType(type, propertyMap, output);
    output.endElement("xsd:complexType");
  }

  /**
   * @see ComponentSerializer#startDeserialize(String, Attributes, Object,
   *      Stack, ClassLoader)
   */
  public Object startDeserialize(String name, Attributes attrs, Object parent,
      Stack objHolderStack, ClassLoader classLoader)
      throws DeserializerException {

    Object obj = null;
    String nullAttr = attrs.getValue("xsi:nil");

    try {
      if (nullAttr == null || nullAttr.equals("false")) {
        Class c = Class.forName(_javaClassName, true, classLoader);
        Class outer = c.getDeclaringClass();
        if (outer == null || Modifier.isStatic(c.getModifiers())) {
          // top level or static inner class
          Constructor ctor = c.getDeclaredConstructor((Class[]) null);
          ctor.setAccessible(true);
          obj = ctor.newInstance((Object[]) null);
        } else {
          // non-static inner class
          Constructor ctor = c.getDeclaredConstructor(new Class[] { outer });
          ctor.setAccessible(true);
          Object declaringObject = findFirstObject(outer, objHolderStack);
          obj = ctor.newInstance(new Object[] { declaringObject });
        }
      }
    } catch (InstantiationException e) {
      throw new DeserializerException("can't instantiate class: "
          + _javaClassName);
    } catch (ClassNotFoundException e) {
      throw new DeserializerException("class not found: " + _javaClassName);
    } catch (IllegalAccessException e) {
      throw new DeserializerException("can't access default ctor of class: "
          + _javaClassName);
    } catch (NoSuchMethodException e) {
      throw new DeserializerException("no default ctor found for class: "
          + _javaClassName);
    } catch (java.lang.reflect.InvocationTargetException e) {
      throw new DeserializerException("exception in initializer: "
          + e.getMessage());
    } catch (java.lang.IllegalArgumentException e) {
      throw new DeserializerException(
          "IllegalArgument exception deserializing " + _javaClassName
              + ", parent " + parent.getClass().getName() + ": "
              + e.getMessage());
    }

    return obj;
  }

  /**
   * @see ComponentSerializer#endDeserialize(Object, String)
   */
  public Object endDeserialize(Object obj, String text)
      throws DeserializerException {

    if (obj != null) {
      obj = readResolveObject(obj);
    }
    return obj;
  }

  /**
   * @see ComponentSerializer#setMember(Object, String, Object)
   */
  public void setMember(Object parent, String name, Object value)
      throws DeserializerException, NoSuchFieldException {

    if (name == null) {
      throw new DeserializerException("member of object must have an name");
    }

    try {
      Field field = getField(parent, name);
      int modifiers = field.getModifiers();
      if (!Modifier.isFinal(modifiers)) { // avoid setting of final fields
        if (Log.isDebugEnabled()) {
          Log.debug("set " + name + " = " + value);
        }
        // set the accessibility to public
        field.setAccessible(true);
        // set the field
        field.set(parent, value);
      }
    } catch (IllegalAccessException e) {
      throw new DeserializerException("Setting " + name + " to "
          + value.getClass().getName() + "(" + value + ")"
          + " threw IllegalAccessException");
    } catch (IllegalArgumentException e) {
      throw new DeserializerException("Setting " + name + " to "
          + value.getClass().getName() + "(" + value + ")"
          + " threw IllegalArgumentException");
    }
  }

  /**
   * Inspect and serialize an object.
   *
   * @param obj
   *          The object to inspect.
   * @param c
   *          The class of the object.
   * @param propertyMap
   *          The output properties.
   * @param objectIdMap
   *          The objects and their ids that were already serialized.
   * @param output
   *          The handler used to write the output.
   * @throws SerializerException
   *           If the inspection of the object failes. If the result can't be
   *           written to the output.
   */
  private void inspectObject(Object obj, Class c, Map propertyMap,
      Map objectIdMap, DocumentOutputHandlerInterface output)
      throws SerializerException {

    // inspect members of super class first
    if (!c.equals(java.lang.Object.class)) {
      inspectObject(obj, c.getSuperclass(), propertyMap, objectIdMap, output);
    }

    // get all fields declared in class
    Field[] fields = getFieldsToSerialize(c, getFieldComparator(c, propertyMap));
    // set the accessibility to public
    AccessibleObject.setAccessible(fields, true);

    // inspect each field
    for (int i = 0; i < fields.length; ++i) {
      String fieldName = fields[i].getName();

      Object member;
      try {
        // get the value of the field
        member = fields[i].get(obj);
      } catch (IllegalAccessException e) {
        throw new SerializerException("can't access element " + fieldName
            + " of class " + c.getName());
      }

      Class fieldType = (member == null) ? fields[i].getType() : member
          .getClass();

      // serialize member
      ComponentSerializer ser = SerializerRegistry.getInstance().getSerializer(
          fieldType);
      ser.serialize(member, fieldType, fieldName, propertyMap, objectIdMap,
          output);
    }
  }

  /**
   * Inspect and serialize an object to JSON.
   * @param obj The object to inspect.
   * @param c The class of the object.
   * @param propertyMap The output properties.
   * @param output The handler used to write the output.
   * @throws SerializerException If the inspection of the object failes.
   * If the result can't be written to the output.
   */
  private int inspectObject(
    Object obj,
    Class c,
    Map propertyMap,
    PrintStream output)
    throws SerializerException {

    int fieldCountParent = 0;
    // inspect members of super class first
    if (!c.equals(java.lang.Object.class)) {
      fieldCountParent = inspectObject(obj, c.getSuperclass(), propertyMap, output);
    }

    // get all fields declared in class
    Field[] fields =
      getFieldsToSerialize(c, getFieldComparator(c, propertyMap));
    // set the accessibility to public
    AccessibleObject.setAccessible(fields, true);
   
    if (fields.length > 0 && fieldCountParent > 0) {
      output.print(",");
    }

    // inspect each field
    for (int i = 0; i < fields.length; ++i) {
      String fieldName = fields[i].getName();

      Object member;
      try {
        // get the value of the field
        member = fields[i].get(obj);
      }
      catch (IllegalAccessException e) {
        throw new SerializerException(
          "can't access element " + fieldName + " of class " + c.getName());
      }

      Class fieldType =
        (member == null) ? fields[i].getType() : member.getClass();
       
      output.print('"');
      printEncodedStr(fieldName, output);
      output.print("\":");
     
      // serialize member
      ComponentSerializer ser =
        SerializerRegistry.getInstance().getSerializer(fieldType);
      ser.toJson(
        member,
        fieldType,
        propertyMap,
        output);
     
      if (i < fields.length - 1) {
        output.print(",");
      }
    }
   
    return fields.length;
  }

  /**
   * Inspect the super types of a class and write according XML extension
   * definitions.
   *
   * @param type
   *          The type to inspect.
   * @param propertyMap
   *          A map (string-to-object) of properties.
   * @param output
   *          The handler used to write the output.
   * @throws SerializerException
   *           If the definition can't be written.
   */
  private void processExtensionType(Class type, Map propertyMap,
      DocumentOutputHandlerInterface output) throws SerializerException {

    Class superclass = type.getSuperclass();
    if (superclass != null) {
      output.startElement("xsd:complexContent");

      ComponentSerializer ser = SerializerRegistry.getInstance().getSerializer(
          superclass);

      AttrImpl attrs = new AttrImpl();
      attrs.addAttribute("base", ser.getXMLTypeName());
      output.startElement("xsd:extension", attrs);

      processFieldTypes(type, propertyMap, output);

      output.endElement("xsd:extension");
      output.endElement("xsd:complexContent");
    } else {
      // it must be java.lang.Object!
      // provide the attributes id and reference
      AttrImpl attrs = new AttrImpl();
      attrs.addAttribute("name", "id");
      attrs.addAttribute("type", "xsd:ID");
      output.startElement("xsd:attribute", attrs);
      output.endElement("xsd:attribute");

      attrs = new AttrImpl();
      attrs.addAttribute("name", "reference");
      attrs.addAttribute("type", "xsd:IDREF");
      output.startElement("xsd:attribute", attrs);
      output.endElement("xsd:attribute");
    }
  }

  /**
   * Inspect all fields of a class and write the according element definitions.
   *
   * @param type
   *          The class to inspect.
   * @param propertyMap
   *          A map (string-to-object) of properties.
   * @param output
   *          The handler used to write the output.
   * @throws SerializerException
   *           If the definition can't be written.
   */
  private void processFieldTypes(Class type, Map propertyMap,
      DocumentOutputHandlerInterface output) throws SerializerException {

    // get all fields declared in type
    Field[] fields = getFieldsToSerialize(type, getFieldComparator(type,
        propertyMap));

    if (fields.length > 0) {
      output.startElement("xsd:sequence");

      // inspect each field
      for (int i = 0; i < fields.length; ++i) {
        String fieldName = fields[i].getName();
        Class fieldType = fields[i].getType();

        // serialize member
        ComponentSerializer ser = SerializerRegistry.getInstance()
            .getSerializer(fieldType);

        AttrImpl attrs = new AttrImpl();
        attrs.addAttribute("name", fieldName);
        attrs.addAttribute("type", ser.getXMLTypeName());
        attrs.addAttribute("nillable", "true");
        attrs.addAttribute("minOccurs", "0");

        output.startElement("xsd:element", attrs);
        output.endElement("xsd:element");
      }

      output.endElement("xsd:sequence");
    }
  }

  /**
   * Call the skaReadResolve method of obj if it has one.
   *
   * @param obj
   *          The object to pass to skaReadResolve.
   * @return The object returned by skaReadResolve or obj itself if no
   *         skaReadResolve method exists.
   * @throws DeserializerException
   *           If the invocation of skaReadResolve produces an exception.
   */
  private Object readResolveObject(Object obj) throws DeserializerException {
    final String methodName = "skaReadResolve";

    try {
      Method activate = obj.getClass().getMethod(methodName, new Class[0]);
      if (Log.isDebugEnabled()) {
        Log
            .debug("about to call: " + methodName + " of class: "
                + _xmlTypeName);
      }

      return activate.invoke(obj, new Object[0]);
    } catch (NoSuchMethodException e) {
      return obj;
    } catch (IllegalAccessException e) {
      throw new DeserializerException("can't access method " + methodName
          + " of class: " + _xmlTypeName);
    } catch (InvocationTargetException e) {
      throw new DeserializerException("exception in " + methodName + ": "
          + e.getMessage());
    }
  }

  /**
   * Call the skaWriteReplace method of obj if one exists.
   *
   * @param obj
   *          The object to pass to skaWriteReplace.
   * @return The object returned by skaWriteReplace or obj itself if no
   *         skaWriteReplace method exists.
   * @throws SerializerException
   *           If the invocation of skaWriteReplace produces an exception.
   */
  private Object writeReplaceObject(Object obj) throws SerializerException {
    final String methodName = "skaWriteReplace";
    try {
      Method activate = obj.getClass().getMethod(methodName, new Class[0]);
      if (Log.isDebugEnabled()) {
        Log
            .debug("about to call: " + methodName + " of class: "
                + _xmlTypeName);
      }

      return activate.invoke(obj, new Object[0]);
    } catch (NoSuchMethodException e) {
      return obj;
    } catch (IllegalAccessException e) {
      throw new SerializerException("can't access method " + methodName
          + " of class: " + _xmlTypeName);
    } catch (InvocationTargetException e) {
      throw new SerializerException("exception in " + methodName + ": "
          + e.getMessage());
    }
  }

  /**
   * Get a comparator to order the fields in a certain order.
   *
   * @param type
   *          The class which fields should be ordered.
   * @param propertyMap
   *          The properties of the transformer.
   * @return The comparator used to compare fields.
   * @throws SerializerException
   *           If the field skaFieldOrder exists but has the wrong signature.
   */
  private Comparator getFieldComparator(Class type, Map propertyMap)
      throws SerializerException {
    Comparator comparator = getSkaFieldOrderComparator(type);
    if (comparator == null) {
      comparator = getLexicalFieldOrderComparator(propertyMap);
    }
    return comparator;
  }

  /**
   * Check if a class has a static field String[] skaFieldOrder. If yes, then
   * construct a comparator from it.
   *
   * @param type
   *          The class to check.
   * @return The comparator or null if no such field exists.
   * @throws SerializerException
   *           If the field exists but has the wrong type and/or modifiers.
   */
  private Comparator getSkaFieldOrderComparator(Class type)
      throws SerializerException {
    final String fieldName = "skaFieldOrder";
    try {
      Field field = type.getDeclaredField(fieldName);
      field.setAccessible(true);
      if (!String[].class.equals(field.getType())) {
        throw new SerializerException(fieldName + " of class: "
            + type.getName() + " must be of type String[]");
      }
      return new SkaFieldOrderComparator((String[]) field.get(null));
    } catch (NoSuchFieldException e) {
      return null;
    } catch (IllegalAccessException e) {
      throw new SerializerException("can't access field " + fieldName
          + " of class: " + type.getName());
    } catch (NullPointerException e) {
      throw new SerializerException(fieldName + " of class: " + type.getName()
          + " must be static");
    }
  }

  /**
   * Check if the property SORT_FIELDS is set in the property map. If yes, then
   * construct a LexicographicalFieldOrderComparator.
   *
   * @param propertyMap
   *          The property map.
   * @return The comparator or null if the property is not set.
   */
  private Comparator getLexicalFieldOrderComparator(Map propertyMap) {
    Comparator comp = null;
    if (PropertyHelper.parseBoolean(propertyMap, PropertyKeys.SORT_FIELDS)) {
      comp = new LexicographicalFieldOrderComparator();
    }
   
    return comp;
  }

  /**
   * Find the first object of type clazz in objStack.
   *
   * @param clazz
   *          The type to search.
   * @param objHolderStack
   *          The stack of holder objects.
   * @return The first object of type clazz in objStack.
   * @throws DeserializerException
   *           If no object of type clazz is in objStack.
   */
  private static Object findFirstObject(Class clazz, Stack objHolderStack)
      throws DeserializerException {
    for (int i = objHolderStack.size() - 1; i >= 0; --i) {
      Object obj = ((ObjectDeserializerHolder) objHolderStack.elementAt(i))
          .getObj();
      if (clazz.isInstance(obj)) {
        return obj;
      }
    }
    throw new DeserializerException("Object of class " + clazz.getName()
        + " not found in object hierarchy.");
  }

  /**
   * @see ComponentSerializer#addUsedClasses(Class, Set)
   */
  public void addUsedClasses(Class base, Set usedClasses)
      throws SerializerException {

    if (usedClasses.contains(base)) {
      return; // already evaluated
    }

    SerializerRegistry reg = SerializerRegistry.getInstance();

    // the class itself
    usedClasses.add(base);

    // fields
    Field[] fields = AbstractSerializer.getFieldsToSerialize(base, null);
    for (int i = 0; i < fields.length; i++) {
      Class type = fields[i].getType();
      ComponentSerializer ser = reg.getSerializer(type);
      ser.addUsedClasses(type, usedClasses);
    }

    // base classes
    base = base.getSuperclass();
    while (base != null) {
      ComponentSerializer ser = reg.getSerializer(base);
      ser.addUsedClasses(base, usedClasses);
      base = base.getSuperclass();
    }
  }

  /**
   * @see CollectionSerializer#toJson(Object, Class, Map, PrintStream)
   */
  public void toJson(Object obj, Class type, Map propertyMap,
      PrintStream output) throws SerializerException {
    if (obj != null) {
      // need to call always writeReplace
      obj = writeReplaceObject(obj);
    }

    if (obj != null) {
      output.print("{");
      inspectObject(obj, obj.getClass(), propertyMap, output);
      output.print("}");
    } else {
      output.print("null");
    }
  }
 

}
TOP

Related Classes of com.skaringa.javaxml.serializers.ObjectSerializer

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.