Wednesday, June 27, 2012

How To Dump File Structure To XML. Part 2.

So, here in Part 1 of the post we prepared XSD schema keeping the data-model of our data we're going to store. Now we're ready to prepare parser, wrap it with some 'business-code' and try out.

Generating JAXB parser classes

Let's do that. First of all locate your JRE (I'm sure you have one). There in /bin folder you should find xjc executable. Copy your schema there (let its name be fstoxml.xsd) and run the command 'xjc fstoxml.xsd'. What's it done? It converted the types described in your schema to the parser classes and put it to the package generated basing on the schema namespace. Check the previous part of the post to see the namespace. It is "http://www.notifymeplease.org/fstoxml/schema" which means the package will be "org.notifymeplease.fstoxml.schema". Refer to xjc help to know how to place the classes to different package.

After xjc finishes processing you find the classes under the folders corresponding to the mentioned package. These are your XML parser. Add them to sources of your Java project and get ready to write business wrapper. One important thing to note: after you have added generated classes to your sources go to XTFSFolderRoot.java and add @XmlRootElement(name = "root") annotation just before class definition. That will indicate that the element is devoted to be the root one.

Dump File Structure To XML

Remind that our business-need is to scan the file-structure (starting from some root) and dump it to XML file, so we need the recursive procedure to walk through the tree and the approach to save it. At the bottom of the page you may find the simple code with the comments describing everything that happens there.

The code fetches the value of root property from the resource bundle. The thing to know about resource bundles is that
- the file extension should always be 'properties'
- if no language code specified in the property file name as the suffix, the default one will be used.
- property file should be placed somewhere in classpath
So for current example we should have conf.properties file under the classpath

Then code creates 'root' object and fills it with the help of recursive procedure that goes across the underlying file system and populates the object dependencies.

P.S. - You may find the project sources here.
P.P.S. - You may find the built application here.
P.P.P.S. - Here is the Part 3 where I'm telling how to reproduce file system from generated XML file
To use the built application you should do the following

1. Unpack the archive to somewhere
2. Make sure you have JRE installed and JRE bin folder in your environment PATH variable
3. In the folder you've unpacked the archive to set up the root folder to scan in conf.properties
4. Run the command 'java -jar fstoxml.jar'. You should now see the generated file. For example the generated file for this project src folder looks like
.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xtfsFolderRoot xmlns="http://www.notifymeplease.org/fstoxml/schema" path="C:\FSToXML\src">
    <folder name="org">
        <folder name="notifymeplease">
            <folder name="fstoxml">
                <file name="FSToXML.java"/>
                <folder name="schema">
                    <file name="ObjectFactory.java"/>
                    <file name="package-info.java"/>
                    <file name="XTFSFile.java"/>
                    <file name="XTFSFolder.java"/>
                    <file name="XTFSFolderRoot.java"/>
                </folder>
            </folder>
        </folder>
    </folder>
</xtfsFolderRoot>

.

Code Snippet.

This is the main code utilizing the parser.

package org.notifymeplease.fstoxml;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.notifymeplease.fstoxml.schema.XTFSFile;
import org.notifymeplease.fstoxml.schema.XTFSFolder;
import org.notifymeplease.fstoxml.schema.XTFSFolderRoot;

public class FSToXML {

 public static void main(String[] arg) {

  ResourceBundle bundle = ResourceBundle.getBundle("conf");
  File root = new File(bundle.getString("root"));

  try {
   // Creating JAXBContext to handle the classes from specified package
   JAXBContext jaxbContext = JAXBContext.newInstance("org.notifymeplease.fstoxml.schema");
   // Create marshaller to save data to disk
   Marshaller marshaller = jaxbContext.createMarshaller();
   marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, new Boolean(true));
   // We should not start from not a directory
   if (!root.isDirectory()) {
    throw new IllegalStateException(
      "Root should represent a folder. Current root: " + root.getCanonicalPath());
   }
   // Creating the main Node. The root of further xml output
   XTFSFolderRoot rootNode = new XTFSFolderRoot();
   // Specify attribute
   rootNode.setPath(root.getCanonicalPath());
   // Populate the content
   long start = System.currentTimeMillis();
   rootNode.getFolderOrFile().addAll(populateBody(root));
   // Save to disk
   marshaller.marshal(rootNode, new FileOutputStream("jaxbOutput.xml"));
   System.out.println("Time spent(ms): " + String.valueOf(System.currentTimeMillis() - start));
  } catch (IOException e) {
   e.printStackTrace();
  } catch (JAXBException e) {
   e.printStackTrace();
  }
 }
 
 /**
  * Recursive method of populating the list 
  * @param root
  * @return
  */
 
 public static List<XTFSFile> populateBody(File root){
  File[] newSet = root.listFiles();
  ArrayList<XTFSFile> newList = new ArrayList<XTFSFile>();
  
  // Having the sequential file (which actually may appear to be a folder)
  for(File item: newSet){
   // Check if it is a file
   if(!item.isDirectory()){
    /**
     * It's a file, so just create new object,
     * set the corresponding name
     * and add it to the list
     */
    XTFSFile newFile = new XTFSFile();
    newFile.setName(item.getName());
    newList.add(newFile);
   }else{
    /**
     * It's a folder. so create folder object,
     * set the name of the folder, and populate the content
     * using the same method we're in now
     */
    XTFSFolder newFolder = new XTFSFolder();
    newFolder.setName(item.getName());
    newFolder.getFolderOrFile().addAll(populateBody(item));
    newList.add(newFolder);
   }
  }
  
  // After all has finished, return the result
  return newList;
 }

}

.