While building one of the java projects i came across properties files. They are kind of configuration files which are easy to use in java as java provide its apis. But with time i found that properties files are limited in a way like it does not have properties of a property or it could not have two or more values for the same property. For quite some time i was looking for a solution but it seems it is a less traveled path. Hence i had tried to develop something similar to properties api but having more...

First let us see properties file api:
In Properties APIs first of all we load properties file using objProperty.load(InputStream is). Then we use getProperty(key, defaultValue) to get the individual property.

We will try to implement the same:
This uses basic java reflection and java xml apis.

XML Configuration File
First of all let us define a xml file having configurations. I defined it in the following way:

<Properties> Root Tag

<PropertiesHeader> Header tag containing application level details
like <ApplicationName> and <LastUpdated>

<Property> Property tag containing the property. This tag has a parameters name and class. The xml can have as many property tag as needed.

Sample xml file looks like:
--------------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<Properties>
<PropertiesHeader>
<ApplicationName>ABC</ApplicationName>
<LastUpdated>02-03-2010 11:30:00</LastUpdated>
</PropertiesHeader>
<Property name="DEF" class="beans.Project">
    <Value name="cvsPath">C:\\CVSPath\\DEF</Value>
    <Value name="completed">N</Value>
</Property>
<Property name="GHI" class="beans.Project">
    <Value name="cvsPath">C:\\CVSPath\\GHI</Value>
    <Value name="completed">Y</Value>
</Property>
</Properties>

--------------------------------------------------------------------------------------

Bean
Now as these property is encapsulated we have to declare a bean for them represented as "beans.Project" in the XML. This class can vary from property to property.

beans/Project.java
--------------------------------------------------------------------------------------
package beans;

public class Project {

    private String cvsPath;
   
    private String completed;

    public String getCvsPath() {
        return cvsPath;
    }

    public void setCvsPath(String cvsPath) {
        this.cvsPath = cvsPath;
    }
   
    public String getCompleted() {
        return completed;
    }

    public void setCompleted(String completed) {
        this.completed = completed;
    }

    @Override
    public String toString() {
        String str = completed + cvsPath;
        return str;
    }
}

--------------------------------------------------------------------------------------

XMLProperties Class

As this much is done we are now ready to implement a class to load properties into the memory.

--------------------------------------------------------------------------------------
package xmlutil;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

public class XMLProperties {
   
    private ContentHandler contentHandler = new XMLPropertiesContentHandler();
   
    private static Map<String, Object> properties = new HashMap<String, Object>();

    public static Map<String, Object> getProperties() {
        return properties;
    }

    public static void setProperties(Map<String, Object> propertiesMap) {
        properties = propertiesMap;
    }

    public void load( String fileName ) throws IOException, SAXException, ParserConfigurationException {
        SAXParserFactory factory = SAXParserFactory.newInstance();
        factory.setValidating(false);
        factory.setNamespaceAware(true);

        SAXParser parser = factory.newSAXParser();
        XMLReader reader = parser.getXMLReader();
        reader.setContentHandler(contentHandler);
        reader.parse(fileName);
    }
   
    public String getProperty(String key,String attribute, String defaultValue) {
        String returnValue = defaultValue;
        Object obj = properties.get(key);
        String methodName = "get" + Character.toUpperCase(attribute.charAt(0)) + attribute.substring(1);
        try {
            Class partypes[] = new Class[0];
            Method method = obj.getClass().getMethod(methodName, partypes);
            Object arglist[] = new Object[0];
            returnValue = (String) method.invoke(obj, arglist);
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NullPointerException npe) {
            return null;
        }
        return returnValue;
    }

    public Object getProperty(String key) {
        Object obj = properties.get(key);
        return obj;
    }

}

--------------------------------------------------------------------------------------

In this class i have used SAX parser to parse the property file as it would be parsed in one flow and there is no traversing needed within XML.

This class defines
A contentHandler for the SAX parser. This will be used to parse the XML.
A static Hashmap which stores the key, value pair where key is a string and value is an object.

Getter & Setter methods of properties map.
Several overloaded getProperty methods.
load method which instantiates SAXParserFactory and set contentHandler which takes care of parsing xml.

ContentHandler Class
Lastly we have to define the content handler and our work is done.

--------------------------------------------------------------------------------------
package xmlutil;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

public class XMLPropertiesContentHandler implements ContentHandler {

    private Locator locator;

    private Class propertyClass;

    private String propertyName;

    private String attributeName;

    public void setDocumentLocator(Locator locator) {
        this.locator = locator;
    }

    public void startDocument() throws SAXException {
       
    }

    public void endDocument() throws SAXException {
    }

    public void processingInstruction(String target, String data)
            throws SAXException {
    }

    public void startPrefixMapping(String prefix, String uri) {
    }

    public void endPrefixMapping(String prefix) throws SAXException {
    }

    public void startElement(String namespaceURI, String localName,
            String qName, Attributes atts) throws SAXException {
        if (localName.equalsIgnoreCase("Properties")) {
        } else if (localName.equalsIgnoreCase("PropertiesHeader")) {
        } else if (localName.equalsIgnoreCase("Property")) {
            for (int i = 0; i < atts.getLength(); i++) {
                if (atts.getQName(i).equalsIgnoreCase("class")) {
                    try {
                        propertyClass = Class.forName(atts.getValue(i));
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
                if (atts.getQName(i).equalsIgnoreCase("name")) {
                    propertyName = atts.getValue(i);
                }
            }
        } else if (localName.equalsIgnoreCase("Value") && propertyClass != null) {
            attributeName = "";
            for (int i = 0; i < atts.getLength(); i++) {
                if (atts.getQName(i).equalsIgnoreCase("name")) {
                    attributeName = atts.getValue(i);
                }
            }
        }
    }

    public void endElement(String namespaceURI, String localName, String qName)
            throws SAXException {
    }

    public void characters(char[] ch, int start, int length)
            throws SAXException {
        Object obj = null;
        String value = showCharacters(ch, start, length);
        if (attributeName != null && !value.trim().equalsIgnoreCase("")) {
            String methodName = "set"
                    + Character.toUpperCase(attributeName.charAt(0))
                    + attributeName.substring(1);
            Class partypes[] = new Class[1];
            partypes[0] = String.class;
            try {
                Method method = propertyClass.getMethod(methodName, partypes);
                Map<String, Object> map = XMLProperties.getProperties();
                obj = map.get(propertyName);
                if (obj == null) {
                    obj = propertyClass.newInstance();
                }
                Object arglist[] = new Object[1];
                arglist[0] = value;
                method.invoke(obj, arglist);
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            if (propertyName != null && obj != null) {
                Map<String, Object> map = XMLProperties.getProperties();
                if (map == null) {
                    map = new HashMap<String, Object>();
                }
                map.put(propertyName, obj);

            }
        }
    }

    public void ignorableWhitespace(char[] ch, int start, int length)
            throws SAXException {
        showCharacters(ch, start, length);
    }

    public void skippedEntity(String name) throws SAXException {
    }

    /**
     * Internal method to format arrays of characters so the special whitespace
     * characters will show.
     */
    public String showCharacters(char[] ch, int start, int length) {
        String characters = "";
        for (int i = start; i < start + length; i++)
            switch (ch[i]) {
            case '\n':
                break;
            case '\r':
                break;
            case '\t':
                break;
            default:
                characters = characters + ch[i];
                break;
            }
        return characters;
    }
}

--------------------------------------------------------------------------------------

This is where we use Reflection APIs to set the values into the required object from the xml.
There are two important methods in this
1 startElement()
    This will be called whenever a start element is detected. It creates a new object of required class where the values need to be set.
2 characters()
   This method is called for each values. This set the values into the respective attribute using its setter method. It internally uses other methods like showCharacters();

This is all about basic XML Properties. This can be extended in many ways. There are several variations of this maybe already existing.

How to use:

--------------------------------------------------------------------------------------
        XMLProperties xmlProperties = new XMLProperties();
        try {
            xmlProperties.load("C:\\properties.xml");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
        System.out.println(XMLProperties.getProperties());
        System.out.println(xmlProperties.getProperty("GHI"));
        System.out.println(
xmlProperties.getProperty("DEF", "completed", "default"));
--------------------------------------------------------------------------------------

Further ...
This implementation can be improved by
1. Proper Exception Handling.
2. More user friendly methods in XMLProperties.
3. Verification of XML can be added based on DTD or XSD.
4. Implementation to include header properties.