Monday, July 25, 2011

READ_WRITE XML in Java Me

Last month (june-2011) I needed to write a mobile application that could read and write xml documents (using JavaMe, of course). As it was the very first time I needed to do something like that, I had to do some research and what I found is that there are plenty lot of sites, documentation and libraries to do it.

XML Quick Review
As you may know, XML is a markup language (eXtensible Markup Language), it's made of tags, just like html, but in XML the tags are defined by you and they have the meaning you want. It was created to be the standar in data interchange between platforms and it has succeded. Microsoft Office uses it, RSS uses it, ATOM uses it, XHTML uses it, SOAP uses it, and so on... For more information about XML, you can visit the following link:


Remember from my previous posts that JSON was created for the same purpose: data interchange between platforms. They both has their pros as we shall see in a minute...

XML vs JSON
Both them are standards for data interchange. I met XML long before JSON, but it wasn't until I learned about web services that I realized its great value! defining data structures that could be used in any platform...wow!

Recently I met JSON and at first I was like "Why another data interchange standard? isn't XML already enough?" Anyway, after reading and using it I kind of like it.

XML JSON
  • Tags make it easy to read the data.
  • Libraries make it easy for creation/maintenance.
  • Popularity, it is supported by other standards (i.e.XHTML).
  • Mature. It was created in the late 90's.
  • No tags, meaning lighter files.
  • There are also libraries for JSON.
  • Lighter to process
  • Gaining ground in Web technologies.
  • For more pros/cons check this post:


    In my opinion, for comunication between servers and mobile clients, I'd prefer using JSON because is lighter than XML. Nevertheless, you should be aware of using both.

    XML Parsers and JavaMe
    As mentioned before, there are plenty lot of libraries to work with XML in JavaMe. A XML parser is a software that is able to read, process and output XML data and it behaves in any of the following:
    • Reads the entire document and creates an in memory representation of it.
    • Push notifications to listeners when reading the entire document.
    • Pulls information from the document as the application requests it, like an Iterator.

    As you may suppose, the third type, Pull parsers, are better for use in JavaMe.

    READ_WRITE XML in Java Me
    In this post we are going to use KXML2 library in order to make use of XML in a sample mobile application. KXML2 is a pull XML parser with a small footprint, ideal for JavaMe. You may download the library or consult the API from the following URL:


    The sample application will create Client objects and will save them in a XML file. Also, the application will read a XML file in order to import some configurations like number of clients to create and the name of the file where the data is going to be exported.

    READ
    Let’s begin. The following XML document, is the configuration file I was talking about:

      
    <?xml version='1.0' encoding='UTF-8' ?>
    <!--Information for runtime-->
    <conf>
     <numClients>3</numClients>
     <fileName>clients.xml</fileName>
    </conf>
    

    As you may see, it has the properties needed at runtime. The next class, is where we are going to load these properties at runtime:

      
    public class Conf {
    
      /** Number of clients that we will create*/
      private int numClients = 0;
      /** Constant name of the number of clients tag*/
      private static final String ATT_NUM_CLIENTS = "numClients";
      /** Name of the XML where the clients are going to be 
          serialized*/
      private String fileName = "";
      /** Constant name of the file name tag*/
      private static final String ATT_FILE_NAME = "fileName";
    
      /**
       * Returns the configuration object for the XML Document 
       * represented by the parser passed as a parameter
       * @param parser KXMLParser of the configuration XML Document
       * @return Configuration object mapped from the KXMLParser
       */
      public static Conf loadFromXml(KXmlParser parser) {
        Conf conf = new Conf();
        try {
          //this will tell us which type of info we are reading 
          //from the XML document
          int eventType = parser.getEventType();
    
          //do it until the end of the file
          while (eventType != XmlPullParser.END_DOCUMENT) {
            //we are only interested in the start tags
            if (eventType == XmlPullParser.START_TAG) {
              //if the start tag is for the number of clients...
              if (parser.getName().equals(ATT_NUM_CLIENTS)) {
                //obtain the text for this property
                conf.setNumberOfClients(
                     Integer.parseInt(parser.nextText()));
              }
              //otherwise, if the start tag is form the file name...
              else if (parser.getName().equals(ATT_FILE_NAME)) {
                //obtain the text for this property
                conf.setNameOfFile(parser.nextText());
              }
            }
            //move to the next info
            eventType = parser.next();
          }
        } catch (Exception ex) {
          ex.printStackTrace();
        }
    
        return conf;
      }
    
      //getters and setters...
    }
    

    The interesting part of this class is the method +loadFromXml(KXmlParser parser):Conf, which is the method that maps the properties of the XML configuration file with the attributes of the Conf class. The constants defined in this class, are the names of the tags used in the XML document.
    Also you can notice how to obtain information from the XML Document through the KXMLParser. If you need another example about gathering information from the XML Parser, you can check this URL:


    Please note that the KXMLParser that we are using in the Conf class, implements the XmlPullParser interface described in the last URL.

    WRITE
    Now let’s take a look at the Client class, where we are going to write our clients information to a XML Document. In this class, we are going to use the following elements:
    • Comments: It’s easy to add comments. Every element in the XML tree extends from the class org.kxml2.kdom.Node, and have this method to add a child:

    Node.addChild(int, int, java.lang.Object):void

    So, for adding a comment to the XML Document, you do:

     
    Document doc = new Document();
    doc.addChild(0,Node.COMMENT, "This is a XML Comment");
    


    • Document Encoding: Very useful when needing i18n for your application. You can achieve this using the org.kxml2.kdom.Document class:

    Document doc = new Document();
    doc.setEncoding("UTF-8");
    


    • XML Element and its value: The org.kxml2.kdom.Node class has a special method to create Elements. After you create an Element you can sets its value adding a child of type TEXT. At the end, don’t forget to add the Element as a child of the Document or as a child of another Element:

    //creates a XML Document
    Document doc = new Document();
    //Creates a XML Element
    Element elem = doc.createElement(null, “NameOfTheElement”);
    //Sets the value of the Element
    elem.addChild(0, Node.TEXT, “ValueOfTheElement”);
    //Adds the Element to the XML Document at the specified index
    doc.addChild(0, Node.ELEMENT, elem);
    


    • Formatting: The library doesn’t format the output of the XML Document by default, so you have to add some formatting as TEXT nodes that can make human readable the XML output .

    Document doc = new Document();
    //adds a new line to the output        
    doc.addChild(0, Node.TEXT, "\n");
    //this comment will appear as a new line in the output of this XML Document
    doc.addChild(1,Node.COMMENT, "This XML is for One Client");
    //adds a new line an a tab space to the output
    doc.addChild(2, Node.TEXT, "\n\t");
    //creates an empty element
    Element elem = doc.createElement(null, “NameOfTheElement”);
    //The element will appear as a new line and with a tab space in the output of this XML Document
    doc.addChild(3, Node.ELEMENT, elemClient);
    

    Do not repeat indexes at the addChild method of a node or you will overwrite added children.

    Following is the Client class:

    import java.util.Vector;
    import org.kxml2.kdom.*;
    
    public class Client {
        /** Constant name of the client tag*/
        private static final String ATT_CLIENT = "Client";
        /** Constant name of the clients tag*/
        private static final String ATT_CLIENTS = "List";
        /** Name of the client*/
        private String name;
        /** Constant name of the attribute name*/
        private static final String ATT_NAME = "name";
        /** Last name of the client*/
        private String lastName;
        /** Constant name of the attribute last name*/
        private static final String ATT_LAST_NAME = "lastName";
        /** Identification of the client*/
        private String id;
        /** Constant name of the attribute id*/
        private static final String ATT_ID = "id";
    
        /**
         * Allows to get the XML Document from the Client passed 
         * as parameter
         * @param client Client object to convert to XML
         * @return XML Document of the Client passed as parameter
         */
        public static Document toXML(Client client) {
            Document doc = new Document();
            doc.setEncoding("UTF-8");
            
            doc.addChild(0, Node.TEXT, "\n");
            doc.addChild(1,Node.COMMENT, 
                         "This XML is for One Client");
    
            Element elemClient = toXMLElement(doc, client);
            doc.addChild(2, Node.TEXT, "\n\t");
            doc.addChild(3, Node.ELEMENT, elemClient);
            
            return doc;
        }
    
        /**
         * This method should be used by this class only, 
         * that's why it is private.
         * Allows to get a XML Element from the Client passed 
         * as parameter
         * @param client Client Object to convert to XML Element
         * @return XML Element representation of the Client passed 
         * as parameter
         */
        private static Element toXMLElement(Document doc
                                          , Client client) {
            Element elem = doc.createElement(null, ATT_CLIENT);
    
            Element elemId = elem.createElement(null, ATT_ID);
            //check if the attribute is set
            if(client.getId() != null 
               && !client.getId().trim().equals(""))
            {
              elemId.addChild(0, Node.TEXT, client.getId());
            }
    
            Element elemName = elem.createElement(null, ATT_NAME);
            //check if the attribute is set
            if(client.getName() != null 
               && !client.getName().trim().equals(""))
            {
              elemName.addChild(0, Node.TEXT, client.getName());
            }
    
            Element elemLastName = 
                   elem.createElement(null, ATT_LAST_NAME);
            //check if the attribute is set
            if(client.getLastName() != null 
               && !client.getLastName().trim().equals(""))
            {
              elemLastName.addChild(0, Node.TEXT
                                   ,client.getLastName());
            }
    
            //add the elements to the Client Element
            elem.addChild(0, Node.TEXT, "\n\t\t");
            elem.addChild(1, Node.ELEMENT, elemId);
            elem.addChild(2, Node.TEXT, "\n\t\t");
            elem.addChild(3, Node.ELEMENT, elemName);
            elem.addChild(4, Node.TEXT, "\n\t\t");
            elem.addChild(5, Node.ELEMENT, elemLastName);
            elem.addChild(6, Node.TEXT, "\n\t");
    
            return elem;
        }
    
        /**
         * Allows to get a XML Document from a Vector of Clients
         * @param clients Vector of clients to convert to XML
         * @return XML Document of the Vector of clients
         */    
        public static Document toXMLs(Vector clients) {
            Document doc = new Document();
            doc.setEncoding("UTF-8");
    
            doc.addChild(0, Node.TEXT, "\n");
            doc.addChild(1,Node.COMMENT
                        ,"This XML is for a list of Clients");
            doc.addChild(2, Node.TEXT, "\n");
            //creates the root ekement and adds it to the document
            Element root = doc.createElement(null, ATT_CLIENTS);
            doc.addChild(3,Node.ELEMENT, root);
            doc.addChild(4, Node.TEXT, "\n");
    
            int j = 0;
            for (int i = 0; i < clients.size(); i++, j+=2) {
                Client client = (Client) clients.elementAt(i);
                //ask for the XML Element for each client
                Element elemClient = toXMLElement(doc, client);
                root.addChild(j, Node.TEXT, "\n\t");
                root.addChild(j+1, Node.ELEMENT, elemClient);
            }
            root.addChild(j, Node.TEXT, "\n");
            return doc;
        }
        
        //getters and setters...
    }
    

    The class has a few attributes to manage information of a client, it also has constants with the name of the XML tag names where the information is going to be added. The static methods, are helpers to obtain the XML Document of a client or from a Vector of clients. You may notice the addition of TEXT nodes with the values “\n\t...”, what we are doing is adding format (“\n” = New Line, “\t” = Tab space) to the output of the XML document as we will see later.

    The MIDlet that we are going to use in order to test the previous classes is a simple MIDlet without any UI. It first will load the properties from the XML file using the FileConnection API and KXML2, and then it will create as many clients as the numClients property says. At the end, we will write the cliens information to a XML file using FileConnection API and KXML2 (again). The MIDlet uses a conf.xml file located at one of the root file system of the device. Remember from our previous post some useful methods when working with FileConnection API.

    //imports...
    
    public class KxmlMidlet extends MIDlet {
    
      public void startApp() {
        //obtain the roots for the FileConnection API
        Vector roots = getRoots();
    
        Vector vClients = new Vector();
        Conf conf = loadXML("file:///" + 
                            roots.firstElement().toString() + 
                            "conf.xml");
    
        //create as many clients as the property says
        Client client = null;
        for (int i = 0; i < conf.getNumberOfClients(); i++) {
          client = new Client();
          client.setId("" + (i+1));
          client.setName("Name for Client " + client.getId());
          client.setLastName(
               "Last Name for Client " + client.getId());
    
          vClients.addElement(client);
        }
    
        //if there are created clienst, write them to a XML File
        if (vClients.size() > 0) {
          Document doc = Client.toXMLs(vClients);
          saveOnDevice("file:///" + 
                        roots.firstElement().toString() + 
                        conf.getNameOfFile(), doc);
        }
      }
    
      
      /**
       * Loads the configuration file located at the specified 
       * location
       * @param path location where the configuration file is at
       * @return Conf object with the configurations
       */
      private Conf loadXML(String path) {
        Conf conf = new Conf();
    
        KXmlParser parser = null;
        InputStream in = null;
        FileConnection file = null;
        try {
          //Access to the file
          file = (FileConnection) Connector.open(path);
          //Checks whether the file exist
          if (!file.exists()) {
            return conf;
          }
    
          in = file.openInputStream();
    
          parser = new KXmlParser();
          //the second parameter is the encoding.
          //Try to use a encoding
          //in order to avoid special characters issues.
          //For example in order to use á,é,í...Ñ... try ISO8859_1
          //You can also try the general one: UTF-8, 
          //but it wont work with á,é...
          parser.setInput(in, "UTF-8");
          //calls the helper method of the Conf class
          conf = Conf.loadFromXml(parser);
    
        } catch (Exception ex) {
          ex.printStackTrace();
        } finally {
    
          if (in != null) {
            try {
              in.close();
            } catch (Exception ex) {
            }
          }
          if (file != null) {
            try {
              file.close();
            } catch (Exception ex) {
            }
          }
        }
    
        return conf;
      }
    
     
      /**
       * Saves the XML Document in the file system of the device
       * @param name Path to file to be created
       * @param doc XML Document to be saved
       */
      public void saveOnDevice(final String name, 
                               final Document doc) {
        //starts a new Thread in order to avoid blocks
        new Thread(new Runnable() {
    
          public void run() {
            //used for writing the XML
            XmlSerializer serializer = null;
    
            FileConnection file = null;
            OutputStreamWriter out = null;
            try {
              file = (FileConnection) Connector.open(name);
              //checks whether the file exist or not
              if (!file.isDirectory() && !file.exists()) {
                file.create();
              }
    
              out = new OutputStreamWriter(file.openOutputStream());
              serializer = new KXmlSerializer();
              serializer.setOutput(out);
              doc.write(serializer);
              out.flush();
              out.close();
    
            } catch (Exception ex) {
              ex.printStackTrace();
            } finally {
              if (out != null) {
                try {
                  out.close();
                } catch (Exception ex) {
                }
              }
              if (file != null) {
                try {
                  file.close();
                } catch (Exception ex) {
                }
              }
            }
          }
        }).start();
      }
    
      /**
       * Asks for the list of drivers where files can be stored
       * @return Vector of Strings representing the name of
       * the drivers where files can be stored
       */
      private Vector getRoots() {
        Vector vRoots = new Vector();
    
        if (checkFileSupport()) {
          Enumeration drivers = FileSystemRegistry.listRoots();
          while (drivers.hasMoreElements()) {
            vRoots.addElement((String) drivers.nextElement());
          }
        }
        return vRoots;
      }
    

    So, after running the MIDlet, we will find a XML file in the file system of the device, with the name specifid by the nameOfFile property and with the information of our clients in XML. Remember from our previous post where is javame saving my files?, if you dont know where to find the XML file that we are talking about.

    Next is the output of this file:

    <?xml version='1.0' encoding='UTF-8' ?>
    <!--This XML is for a list of Clients-->
    <List>
     <Client>
      <id>1</id>
      <name>Name for Client 1</name>
      <lastName>Last Name for Client 1</lastName>
     </Client>
     <Client>
      <id>2</id>
      <name>Name for Client 2</name>
      <lastName>Last Name for Client 2</lastName>
     </Client>
     <Client>
      <id>3</id>
      <name>Name for Client 3</name>
      <lastName>Last Name for Client 3</lastName>
     </Client>
    </List>

    You may notice it is human readable, because we added formatting texts during the XML Document creation.

    Here is another file, without using formatting text:

    <?xml version='1.0' encoding='UTF-8' ?><!--This XML is for a list of Clients--><List><Client><id>1</id><name>Name for Client 1</name><lastName>Last Name for Client 1</lastName></Client><Client><id>2</id><name>Name for Client 2</name><lastName>Last Name for Client 2</lastName></Client><Client><id>3</id><name>Name for Client 3</name><lastName>Last Name for Client 3</lastName></Client></List>
    

    So it’s up to you if you need formatting or not.

    KXML2 is working on a newer version of the library, you should check its website in order to be updated with the latest version available.
    I liked using KXML2, and I think it is very useful when your device doesn’t have any embedded API to work with XML. Some new devices nowadays come with an API to work with XML and Web Services called JSR172. In a future post we will remake this clients example using the features provided by JSR172.

    OK, quite a large post, but I hope it can help you to work with XML on your mobile applications.

    see ya soon!

    References:

    KXML. [online].
    Available on Internet: http://kxml.sourceforge.net/kxml2/
    [accessed on June 12 2011].

    XML Pull Parsing. March 2005. XML Pull .org [online].
    Available on Internet: http://www.xmlpull.org/
    [accessed on June 12 2011].

    KXML: A Great Find for XML Parsing in J2ME. April 2003. DevX.com [online].
    Available on Internet: http://www.devx.com/xml/Article/11773
    [accessed on June 12 2011].