0

I'm attempting to write a small library which will convert Java objects, through reflection, into XML. I've got the majority of it working, but ran into an error when attempting to iterate through an array.

Here are the domain objects i'm using for testing:

In Company.java:

import java.util.List;

import com.google.common.collect.Lists;

public class Company
{
    public Employee employeeArray[];

    public Employee[] getEmployeeArray()
    {
        return employeeArray;
    }

    public void setEmployeeArray(Employee[] employeeArray)
    {
        this.employeeArray = employeeArray;
    }
}

In Employee.java:

public class Employee
{
    public String firstName;
    public String lastName;

    public Employee() {}

    public Employee(String firstName, String lastName)
    {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName()
    {
        return firstName;
    }

    public void setFirstName(String firstName)
    {
        this.firstName = firstName;
    }

    public String getLastName()
    {
        return lastName;
    }

    public void setLastName(String lastName)
    {
        this.lastName = lastName;
    }
}

The core of the library (ObjectXMLWriter.java):

import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.List;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.falcondev.web.DOMFactory;
import com.google.common.collect.Lists;

public class ObjectXMLWriter
{
    private static final Logger logger = Logger.getLogger(ObjectXMLWriter.class);

    private String fileLocation;
    private Object object;
    private boolean shouldOverride;

    public ObjectXMLWriter(String fileLocation, Object object) {
        this.fileLocation = fileLocation;
        this.object = object;
        this.shouldOverride = false;
    }

    public ObjectXMLWriter(String fileLocation, Object object, boolean shouldOverride) {
        this(fileLocation, object);
        this.shouldOverride = shouldOverride;
    }

    public String getFileLocation()
    {
        return fileLocation;
    }

    public void setFileLocation(String fileLocation)
    {
        this.fileLocation = fileLocation;
    }

    public Object getObject()
    {
        return object;
    }

    public void setObject(Object object)
    {
        this.object = object;
    }

    public boolean isShouldOverride()
    {
        return shouldOverride;
    }

    public void setShouldOverride(boolean shouldOverride)
    {
        this.shouldOverride = shouldOverride;
    }

    public boolean saveObject() throws Exception {

        boolean saveSuccessful = false;
        Document document = createDocument();

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

        StringWriter stringWriter = new StringWriter();
        StreamResult result = new StreamResult(stringWriter);
        DOMSource source = new DOMSource(document);
        transformer.transform(source, result);
        String xmlString = stringWriter.toString();

        //print XML
        System.out.println("Here's the xml:\n\n" + xmlString);

        //TODO save XML file
        return saveSuccessful;
    }

    private Node createNode(Document document, Object object) throws Exception {
        Node node = document.createElement(getObjectClassName(object));
        logger.trace("NODE: " + node);

        if(node != null) {
            //create children nodes from object fields
            List<Field> fields = Lists.newArrayList(object.getClass().getFields());
            for(Field field: fields) {
                Object obj = field.get(object);
                logger.trace("OBJECT: " + obj);

                if(obj == null || !checkInstantiability(obj) || field.getType().isAssignableFrom(List.class) || field.getType().isArray()) {
                    logger.debug("ATTEMPTING TO CREATE NODE FOR FIELD: " + field.getName());
                    logger.debug("FIELD TYPE: " + field.getType());

                    //TODO add types as needed
                    String fieldValue = "";
                    if(List.class.isAssignableFrom(field.getType())) { //TODO check if object is iterable instead
                      //TODO figure out how to iterate through iterable's
                    }
                    else if(field.getType().isArray()) {
                        Object array = field.get(obj); //Fails here
                        int length = Array.getLength(array);
                        for (int i = 0; i < length; i++) {
                          System.out.println(Array.get(array, i));
                          node.appendChild(createNode(document, Array.get(array, i)));
                        }
                    }
                    else if(field.getType() == Class.class) {
                        fieldValue = obj.toString().replaceFirst("class ", "");
                    }
                    else {
                        fieldValue = obj.toString();
                    }
                    logger.debug("FIELD OBJECT VALUE: '" + fieldValue + "'");

                    //TODO check for annotation to choose whether to create element or attribute
                    Element element = document.createElement(field.getName());
                    element.setTextContent(fieldValue);
                    node.appendChild(element);
                }
                else {
                    logger.debug("ATTEMPTING TO CREATE OBJECT NODE FOR FIELD: " + field.getName());
                    node.appendChild(createNode(document, obj));
                }
            }
        }

        return node;
    }

    private Document createDocument() throws Exception {
        Document document = DOMFactory.create();
        if(checkInstantiability(object)) {
            Node rootNode = createNode(document, object);
            document.appendChild(rootNode);
        }
        else {
            logger.error("CANNOT SAVE UNINSTANTIABLE OBJECT. DOCUMENT MUST HAVE A ROOT NODE");
        }

        return document;
    }

    private String getObjectClassName(Object object) {
        return object.getClass().getSimpleName().toLowerCase();
    }

    private boolean checkInstantiability(Object object) {
        try
        {
            object.getClass().newInstance();
        }
        catch (InstantiationException exception)
        {
            return false;
        }
        catch (IllegalAccessException exception)
        {
            return false;
        }

        return true;
    }
}

And the Test driver (Test.java):

public class Test
{
    public static void main(String[] args) throws Exception
    {
        Employee[] employees = new Employee[3];
        employees[0] = new Employee("Tom", "C");
        employees[1] = new Employee("Paul", "E");
        employees[2] = new Employee("George", "A");
        Company company = new Company();
        company.setEmployeeArray(employees);

        new ObjectXMLWriter("resources/test2.xml", company).saveObject();
    }
}

The error I get when running this code is as follows:

Exception in thread "main" java.lang.IllegalArgumentException: Can not set [Lcom.falcondev.orm.test.Employee; field com.falcondev.orm.test.Company.employeeArray to [Lcom.falcondev.orm.test.Employee;
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:146)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:150)
    at sun.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:37)
    at sun.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:18)
    at java.lang.reflect.Field.get(Field.java:358)
    at com.falcondev.orm.ObjectXMLWriter.createNode(ObjectXMLWriter.java:116)
    at com.falcondev.orm.ObjectXMLWriter.createDocument(ObjectXMLWriter.java:149)
    at com.falcondev.orm.ObjectXMLWriter.saveObject(ObjectXMLWriter.java:74)
    at com.falcondev.orm.test.Test.main(Test.java:37)

I realize this is quite a bit of code to post. I tried the following example as shown in this post (http://stackoverflow.com/questions/2200399/iterating-over-arrays-by-reflection/2200493#2200493), and it worked fine, so there must be some subtle difference from the simple example and what I am doing in the above code.

Simple Test which works:

public class Test
{

    public static void main(String[] args) throws Exception
    {
        Employee[] employees = new Employee[3];
        employees[0] = new Employee("Tom", "C");
        employees[1] = new Employee("Paul", "E");
        employees[2] = new Employee("George", "A");
        Company company = new Company();
        company.setEmployeeArray(employees);

        Field field = company.getClass().getField("employeeArray");
        if (field.getType().isArray()) {
          Object array = field.get(company);
          int length = Array.getLength(array);
          for (int i = 0; i < length; i++) {
            System.out.println(Array.get(array, i));
          }
        }
    }
}

As for technologies info, for this example I am using: windows 7, eclipse 3.7, jdk 1.6.0_26, log4j 1.2.16, apache commons-lang3-3.0.1, and google guava 10.0

I've been trying to get this to work for the past few weeks, so any help would be greatly appreciated.

Edit:

For future reference and other's use, the relevant code to fix the issue is as follows:

boolean shouldSaveFieldValue = true;

if(... || field.getType() == String.class) {
  ..    
  else if(field.getType().isArray()) {
    Object array = field.get(object);
    ...
    shouldSaveFieldValue = false;
  }

  if(shouldSaveFieldValue) {
    ...
    Element element = document.createElement(field.getName());
    element.setTextContent(fieldValue);
    node.appendChild(element);
  }
}
nickg
  • 3
  • 1
  • 4
  • What is the logical difference between the list and the array that you have? – corsiKa Oct 22 '11 at 22:39
  • @glowcoder If your talking about the employees list field and the employeeArray array field. I was originally trying to get this to work with lists, I accidentally left the employees list field in Employee.java and in the title of my post. It would be nice to get this working with lists as well, but I can deal with it using arrays only. Will edit my post. – nickg Oct 22 '11 at 23:02
  • 3
    There are only about a dozen open-source libraries that do a complete job of this already. You might be happier with JAX-B, or XML-Beans, or Aegis from Apache CXF, than writing your own. – bmargulies Oct 22 '11 at 23:03
  • @bmargulies These look like they could work, but I was intentionally avoiding other libraries. Looked at JAX-B breifly but it looked somewhat complicated and my aim was to write a simple library. If I ever have a need of a more complicated library, I will definitely look into using some of those libraries you mentioned instead of writing a bunch more functionality in to this. – nickg Oct 23 '11 at 00:19
  • @nickg - The following example will help you out with JAXB: http://wiki.eclipse.org/EclipseLink/Examples/MOXy/GettingStarted – bdoughan Oct 23 '11 at 01:34

2 Answers2

3

First, I second the recommendation to use a library rather than rolling your own. I've had really good luck with JAXB which doesn't event require a third party jar. To your question, I think you're simply getting confused between the variable names "object" and "obj":

Company object = a Company
Field field = [ Company.Employee[] employeeArray ]
Employee[] obj = object.employeeArray

field.get(obj) cannot be evaluated because the parameter is not of type Company. Here's the relevant bits of code:

private Node createNode(Document document, Object object) {
    ..
    List<Field> fields = Lists.newArrayList(object.getClass().getFields());
        for(Field field: fields) {
        ...
        Object obj = field.get(object);
        ...
        else if(field.getType().isArray()) {
            Object array = field.get(obj); //Fails here
kylewm
  • 634
  • 6
  • 21
  • +1 At the failing line, `obj` should already be the array, and the error is just unintuitively worded. – millimoose Oct 22 '11 at 23:37
  • This is exactly what needed to be changed. I had to change a couple of other small things to get it to process Strings properly and to not write in the extra tag "" tag in at the end of the array, and will post these in a minute for future reference and for others to use. Thanks! – nickg Oct 23 '11 at 00:28
-1

Change:

public Employee employeeArray[];

to:

public transient Employee employeeArray[];

As far as I know, arrays are not serializable, which, I assume, is why you have an employee list as well as an array. So this tells the serializer to ignore the employee array when serializing the object.

orangething
  • 708
  • 5
  • 16